From 502f271f36f1ffc29598f14ab465a512a4ff9cd8 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 25 Aug 2026 14:06:41 +0200 Subject: [PATCH] Einheitliche Sim-Kopfzeile (Entwurf B) als Komponente + Lizenz-Datenmodell - ggs-header.css/.js: schwebende Pillen-Kopfzeile aus vorhandenem .ggs-header-Markup (Kreis-Logo/Titel/Level als eine Pille, eigene SVG-Icons, Tempo-Panel links + Zustandsbutton, Musik-Panel unten, 30% transparente Untermenues, Glossar target=_blank, Lehrplan raus). Enhance-Ansatz: Spiel-Logik (IDs/Klassen, data-speed) bleibt unberuehrt. - Eingebaut in Klima 2D/3D, Fluss, Logistik (Auto-Tempo), Energiemanager, Kofferdetektiv 3D. - admin-licenses.html: Datenmodell-Sektion (licenses-Tabelle, Lebenszyklus Generiert->Eingeloest->Zugewiesen, Beziehungen, Jahr->Schuljahr->Code). Co-Authored-By: Claude Opus 4.8 --- App/admin-licenses.html | 94 +++++++++ App/assets/css/ggs-header.css | 215 +++++++++++++++++++++ App/assets/js/ggs-header.js | 274 +++++++++++++++++++++++++++ App/sims/energiemanager/game.html | 4 +- App/sims/fluss/game.html | 4 +- App/sims/klima/game-2d.html | 4 +- App/sims/klima/game-3d.html | 4 +- App/sims/kofferdetektiv/game-3d.html | 4 +- App/sims/logistik/game.html | 4 +- 9 files changed, 601 insertions(+), 6 deletions(-) create mode 100644 App/assets/css/ggs-header.css create mode 100644 App/assets/js/ggs-header.js diff --git a/App/admin-licenses.html b/App/admin-licenses.html index c327fab..1c5ff97 100644 --- a/App/admin-licenses.html +++ b/App/admin-licenses.html @@ -35,6 +35,33 @@ .toolbar{display:flex;gap:.4rem;margin-bottom:.8rem;flex-wrap:wrap} .msg{font-size:.72rem;padding:.4rem .6rem;border-radius:6px;margin-top:.5rem} .msg.ok{background:#dceadd;color:#5a8a5e} + /* ---- Datenmodell-Sektion ---- */ + .dm-lead{font-size:.74rem;color:#4a4a4a;line-height:1.55;margin-bottom:.6rem} + .dm-sub{font-size:.6rem;font-weight:800;color:#4a7c8a;text-transform:uppercase;letter-spacing:.05em;margin:1.2rem 0 .5rem} + .dm-note{font-size:.66rem;color:#8a6a2a;background:#fdf6e3;border:1px solid #efe0b8;border-radius:6px;padding:.45rem .6rem;margin-top:.5rem;line-height:1.5} + .dm-flow{display:flex;gap:.4rem;align-items:stretch;flex-wrap:wrap} + .dm-step{flex:1;min-width:190px;background:#f7f6f3;border-radius:8px;border-left:4px solid #c4a97a;padding:.65rem .75rem} + .dm-step.s-pool{border-left-color:#b8b2a6} + .dm-step.s-redeem{border-left-color:#4a7c8a} + .dm-step.s-assign{border-left-color:#5a8a5e} + .dm-step .s-top{display:flex;align-items:center;gap:.4rem;margin-bottom:.35rem} + .dm-step .s-n{width:18px;height:18px;border-radius:50%;background:#4a7c8a;color:#fff;font-size:.62rem;font-weight:800;display:flex;align-items:center;justify-content:center;flex:none} + .dm-step.s-pool .s-n{background:#b8b2a6} + .dm-step.s-assign .s-n{background:#5a8a5e} + .dm-step .s-t{font-size:.74rem;font-weight:800;color:#2f3b3f} + .dm-step .s-who{font-size:.6rem;font-weight:700;color:#8a8a8a;text-transform:uppercase;letter-spacing:.03em} + .dm-step .s-d{font-size:.68rem;color:#4a4a4a;line-height:1.45;margin-top:.15rem} + .dm-step code{font-family:'Courier New',monospace;font-size:.66rem;background:rgba(0,0,0,.05);padding:.05rem .3rem;border-radius:4px;color:#c85c4a} + .dm-arrow{display:flex;align-items:center;justify-content:center;color:#c4a97a;font-size:1.4rem;font-weight:900;padding:0 .1rem} + .dm-er{display:flex;align-items:center;gap:.35rem;flex-wrap:wrap;font-size:.68rem} + .dm-ent{background:#fff;border:1.5px solid #4a7c8a;color:#4a7c8a;border-radius:6px;padding:.3rem .55rem;font-weight:800;font-family:'Courier New',monospace} + .dm-ent.e-lic{background:#4a7c8a;color:#fff} + .dm-rel{color:#8a8a8a;display:flex;flex-direction:column;align-items:center;line-height:1;font-family:'Courier New',monospace;font-size:.58rem} + .dm-rel b{color:#c4a97a;font-size:1.1rem} + .dm-derive{background:#f7f6f3;border-radius:8px;padding:.7rem .8rem;font-size:.7rem;line-height:1.7} + .dm-derive .d-code{font-family:'Courier New',monospace;font-weight:800;color:#4a7c8a;letter-spacing:.04em} + .dm-derive .d-hi{background:#e8c547;color:#4a4a4a;padding:.02rem .28rem;border-radius:4px;font-weight:800} + td.mono{font-family:'Courier New',monospace;font-size:.68rem;color:#c85c4a} @@ -104,6 +131,73 @@ + + +
+
📐 Datenmodell — Lizenznummern
+

Eine Lizenz = ein Sitzplatz für eine:n Schüler:in in einem Schuljahr. Der Admin erzeugt einen Vorrat an Codes, eine Lehrperson löst Codes ein (sie werden ihrem Konto zugeordnet) und weist sie dann einzelnen Schüler:innen zu. Alles steht in einer Tabelle licenses — die drei Zustände ergeben sich allein aus zwei Feldern: teacher_id und student_id.

+ +
Tabelle „licenses"
+ + + + + + + + + +
SpalteTypBedeutung
idINT · PKInterne ID
codeVARCHAR · UNIQUEDer Lizenzcode, Format XXXX-XXXX-XXXX-JAHR
school_yearVARCHARSchuljahr, z. B. „26/27" — aus dem Jahr abgeleitet
teacher_idFK → teachersEinlösende Lehrperson · NULL = noch im Pool
student_idFK → studentsPlatz-Inhaber:in · NULL = noch frei
redeemed_atTIMESTAMP · NULLZeitpunkt des Einlösens durch die Lehrperson
created_atTIMESTAMPErstellung (Admin-Generierung)
+ +
Lebenszyklus & Einlösen
+
+
+
1Generiert
+
Admin
+
teacher_id = NULL · student_id = NULL. Frisch erzeugter Code im Pool.
Aktion: generate
+
+
+
+
2Eingelöst
+
Lehrperson
+
teacher_id gesetzt · redeemed_at = jetzt · student_id noch NULL. Code gehört nun der Lehrperson.
Aktion: redeem / redeem_batch
+
+
+
+
3Zugewiesen
+
Lehrperson
+
student_id gesetzt. Der Platz ist von einer konkreten Person belegt.
Aktion: assign / auto_assign
+
+
+
⚠️ In der Statistik oben zählt „Frei" = student_id IS NULL — also auch schon eingelöste, aber noch nicht zugewiesene Codes. „Zugewiesen" = student_id gesetzt. Ob ein freier Code schon einer Lehrperson gehört, sieht man an teacher_id / redeemed_at.
+ +
Beziehungen
+
+ licenses + teacher_id + teachers +
+
+ licenses + student_id + students + class_id + classes + teacher_id + teachers +
+

licenses.teacher_id merkt sich die einlösende Lehrperson, licenses.student_id den Platz-Inhaber. Über students.class_id → classes.teacher_id hängt jede:r Schüler:in ohnehin an einer Klasse einer Lehrperson — der direkte teacher_id-Eintrag macht aber auch eingelöste, noch nicht zugewiesene Codes eindeutig einer Lehrperson zuordenbar.

+ +
Jahr → Schuljahr → Code
+
+ Eingabe beim Generieren: Jahr = 2026 (Kalenderjahr, in dem das Schuljahr beginnt).
+ → school_year = „letzte 2 Ziffern"  26  /  26+1  = 26/27
+ → Code-Suffix = das volle Jahr: -2026
+ → Zeichen-Alphabet (ohne Verwechsler): A–Z ohne I,O + 2–9 (kein 0, kein 1)
+ → 3 Blöcke à 4 Zeichen + Jahr  ⟶  Beispiel: K7QF-3M9P-RT2W-2026  ·  Schuljahr 26/27 +
+
Das Jahr steckt fest im Code (Suffix) und bestimmt school_year. Für ein neues Schuljahr wird ein neuer Vorrat generiert; Filter/Export laufen über school_year.
+