Didaktisches Upgrade: Interaktive Parsons-Puzzles, Traceback-Dolmetscher & offizielle Abschluss-Zertifikate

This commit is contained in:
Alex Mueller 2026-08-29 15:40:09 +02:00
parent e74c243259
commit c428db6a5c
36 changed files with 740 additions and 6 deletions

View file

@ -10,5 +10,5 @@
"streak_tage": 1, "streak_tage": 1,
"letzter_aktiver_tag": "2026-08-29", "letzter_aktiver_tag": "2026-08-29",
"historie": [], "historie": [],
"zuletzt_aktualisiert": "2026-08-29T15:07:09.451191" "zuletzt_aktualisiert": "2026-08-29T15:40:02.637013"
} }

59
assets/certificate.js Normal file
View file

@ -0,0 +1,59 @@
/**
* 🎓 OFFIZIELLES LEISTUNGS-ZERTIFIKAT 🎓
* =======================================
* Generiert ein ausdruckbares Abschlusszertifikat nach Abschluss eines Lehrpfads.
*/
(function () {
window.zeigeZertifikatModal = function (lehrpfadNummer) {
const data = window.GAMIFICATION_DATA || { spieler_name: "Python-Entwicklerin", level: 1 };
const dateStr = new Date().toLocaleDateString("de-DE", { day: "2-digit", month: "long", year: "numeric" });
let titel = "Grundlagen der Python-Programmierung";
let moduleDesc = "10 Module: Zahlen & Rechnen, Datentypen, f-Strings, Kontrollstrukturen, Funktionen, Listen, Strings, Dictionaries, Sets & Comprehensions";
if (lehrpfadNummer === 2) {
titel = "Einstieg in die Objektorientierung (OOP) & Entwickler-Tools";
moduleDesc = "9 Module: Klassen, Konstruktor __init__, self, Methoden, UML-Diagramme, VS Code, Git-Versionskontrolle, Komposition & Tamagotchi-Projekt";
} else if (lehrpfadNummer === 3) {
titel = "Fortgeschrittenes OOP, Softwarequalität & Desktop-GUIs";
moduleDesc = "10 Module: Speicher-Referenzen, Dunder-Methoden, TDD Unit Testing, Vererbung, Polymorphie, Exceptions, JSON-Persistenz, Tkinter GUIs, Container & Masterprojekt";
} else if (lehrpfadNummer === "master") {
titel = "Master of Python Software Engineering";
moduleDesc = "Vollständiger Abschluss aller 3 Lehrpfade (27 Module, 188 bestandene automatisierte Unittests, Model-View-Controller Desktop-App)";
}
const modal = document.createElement("div");
modal.className = "certificate-overlay";
modal.innerHTML = `
<div class="certificate-container">
<div class="certificate-border">
<div class="certificate-inner">
<div class="certificate-badge">🎓 OFFIZIELLES ZERTIFIKAT 🎓</div>
<h1 class="certificate-headline">Erfolgreicher Abschluss</h1>
<p class="certificate-sub">Hiermit wird bescheinigt, dass</p>
<div class="certificate-student-name">${data.spieler_name || "Python-Entwicklerin"}</div>
<p class="certificate-text">den anspruchsvollen Lehrpfad</p>
<h2 class="certificate-track-title">${titel}</h2>
<p class="certificate-desc">${moduleDesc}</p>
<div class="certificate-meta">
<div>
<strong>Erreichtes Level:</strong> Level ${data.level || 1} (${data.titel || "Entwicklerin"})
</div>
<div>
<strong>Datum:</strong> ${dateStr}
</div>
</div>
<div class="certificate-seal">🏆 EXZELLENZ-SIEGEL</div>
</div>
</div>
<div class="certificate-actions">
<button onclick="window.print()" class="btn" style="background: var(--success);">🖨️ Zertifikat drucken / Als PDF speichern</button>
<button onclick="document.querySelector('.certificate-overlay').remove()" class="btn btn-secondary">Schließen</button>
</div>
</div>
`;
document.body.appendChild(modal);
};
})();

View file

@ -11,7 +11,7 @@ window.GAMIFICATION_DATA = {
"streak_tage": 1, "streak_tage": 1,
"letzter_aktiver_tag": "2026-08-29", "letzter_aktiver_tag": "2026-08-29",
"historie": [], "historie": [],
"zuletzt_aktualisiert": "2026-08-29T15:07:09.451191" "zuletzt_aktualisiert": "2026-08-29T15:40:02.637013"
}; };
window.GAMIFICATION_BADGES_KATALOG = { window.GAMIFICATION_BADGES_KATALOG = {
"rechenkonegin": { "rechenkonegin": {

170
assets/interpreter.js Normal file
View file

@ -0,0 +1,170 @@
/**
* 🩺 PYTHON TRACEBACK & FEHLER-DOLMETSCHER 🩺
* ============================================
* Basiert auf der didaktischen Fehlerforschung (Brett Becker et al., SIGCSE).
* Übersetzt kryptische Python-Fehlermeldungen in einfühlsames, klares Deutsch
* mit konkreten Handlungsanweisungen für Schülerinnen und Schüler.
*/
(function () {
const ERROR_RULES = [
{
type: "IndentationError",
pattern: /IndentationError: (expected an indented block|unindent does not match)/i,
title: "📐 Einrückungsfehler (IndentationError)",
explanation: "Python nutzt Einrückungen (Standard: genau 4 Leerzeichen), um zu erkennen, welcher Code zu einer Funktion, `if`-Bedingung oder Schleife gehört.",
causes: [
"Nach einem Doppelpunkt (<code>def ...:</code>, <code>if ...:</code>, <code>for ...:</code>) fehlt die Einrückung.",
"Leerzeichen und Tabulatoren wurden versehentlich gemischt."
],
fix: "Drücke nach dem Doppelpunkt <kbd>Enter</kbd> und rücke die Zeile mit <kbd>Tab</kbd> oder 4 Leerzeichen ein. Mit <kbd>Umschalt</kbd> + <kbd>Tab</kbd> kannst du Zeilen wieder nach links rücken."
},
{
type: "TypeError NoneType",
pattern: /TypeError: 'NoneType' object is not (subscriptable|iterable|callable)/i,
title: "🕳️ Zugriff auf 'None' (TypeError)",
explanation: "Du versuchst auf ein Objekt zuzugreifen, das gar nicht existiert oder den Wert <code>None</code> hat.",
causes: [
"Eine aufgerufene Funktion hat vergessen, ihr Ergebnis mit <code>return</code> zurückzugeben.",
"Eine Listen-Methode wie <code>liste.sort()</code> oder <code>liste.append()</code> gibt immer <code>None</code> zurück (sie verändert die Liste direkt!)."
],
fix: "Prüfe in deinen Funktionen, ob am Ende ein <code>return ergebnis</code> steht. Schreibe niemals <code>x = liste.sort()</code>, sondern nur <code>liste.sort()</code>."
},
{
type: "AttributeError",
pattern: /AttributeError: '([^']+)' object has no attribute '([^']+)'/i,
title: "🔍 Unbekanntes Attribut oder Methode (AttributeError)",
explanation: "Python konnte an dem Objekt die gewünschte Methode oder Eigenschaft nicht finden.",
causes: [
"Tippfehler im Namen der Methode oder Variable (z.B. <code>anhängen</code> statt <code>append</code>).",
"Groß- und Kleinschreibung nicht beachtet (z.B. <code>.Name</code> statt <code>.name</code>).",
"In der Klassen-Methode wurde <code>self.</code> vergessen (z.B. <code>alter</code> statt <code>self.alter</code>)."
],
fix: "Überprüfe die Schreibweise des Attributs exakt Buchstabe für Buchstabe. Nutze in Klassen immer <code>self.attributname</code>."
},
{
type: "TypeError self missing",
pattern: /TypeError: ([^\(]+)\(\) takes \d+ positional arguments? but \d+ were given/i,
title: "🧩 Fehlendes 'self' in Methoden-Definition (TypeError)",
explanation: "Eine Methode in einer Klasse erwartet weniger oder mehr Parameter als übergeben wurden.",
causes: [
"Der klassische Anfängerfehler: Du hast im Methodenkopf das <code>self</code> als ersten Parameter vergessen! (z.B. <code>def fahren():</code> statt <code>def fahren(self):</code>)."
],
fix: "Schreibe in jeder Methode deiner Klasse als allerersten Parameter immer <code>self</code>: z.B. <code>def beschleunigen(self, kmh):</code>."
},
{
type: "IndexError",
pattern: /IndexError: (list index out of range|string index out of range)/i,
title: "🎯 Index außerhalb des gültigen Bereichs (IndexError)",
explanation: "Du greifst auf eine Position (Index) in einer Liste oder einem String zu, die gar nicht existiert.",
causes: [
"Off-by-One Fehler: In Python beginnt die Zählung bei <code>0</code>! Eine Liste mit 3 Elementen hat nur die Indizes <code>0, 1, 2</code> (nicht 3).",
"Die Liste ist noch leer."
],
fix: "Prüfe die Länge der Liste mit <code>len(liste)</code>. Das letzte Element erreichst du immer sicher mit <code>liste[-1]</code>."
},
{
type: "KeyError",
pattern: /KeyError: (.+)/i,
title: "🔑 Schlüssel nicht im Dictionary gefunden (KeyError)",
explanation: "Du hast versucht, aus einem Dictionary <code>dict[schluessel]</code> einen Eintrag abzufragen, der nicht existiert.",
causes: [
"Der gesuchte Key ist nicht im Dictionary vorhanden oder falsch geschrieben."
],
fix: "Nutze die sichere Abfrage mit <code>dict.get(key, standardwert)</code> statt der eckigen Klammern, um Abstürze zu vermeiden!"
},
{
type: "NameError",
pattern: /NameError: name '([^']+)' is not defined/i,
title: "❓ Name nicht definiert (NameError)",
explanation: "Python kennt das Wort oder die Variable an dieser Stelle nicht.",
causes: [
"Die Variable wurde weiter unten oder in einer anderen Funktion definiert (Gültigkeitsbereich/Scope).",
"Ein Tippfehler im Variablennamen.",
"Ein Modul wie <code>import math</code> wurde noch nicht importiert."
],
fix: "Prüfe, ob du die Variable vor ihrer Benutzung angelegt hast und ob alle benötigten Module oben mit <code>import ...</code> eingebunden sind."
},
{
type: "ZeroDivisionError",
pattern: /ZeroDivisionError: division by zero/i,
title: "➗ Division durch Null (ZeroDivisionError)",
explanation: "In der Mathematik und in Python ist das Teilen durch <code>0</code> nicht erlaubt.",
causes: [
"Der Nenner / Teiler hat den Wert <code>0</code>."
],
fix: "Sichere die Division mit einer <code>if teiler != 0:</code> Bedingung oder fange sie mit <code>try-except ZeroDivisionError:</code> ab."
}
];
window.interpretiereTraceback = function (errorText) {
if (!errorText || !errorText.trim()) {
return {
matched: false,
title: "💡 Bitte füge eine Fehlermeldung ein",
explanation: "Kopiere deinen Traceback aus dem Terminal und füge ihn hier ein.",
fix: "Starte dein Skript im Terminal mit python3 test_aufgabe.py"
};
}
for (let rule of ERROR_RULES) {
if (rule.pattern.test(errorText)) {
return {
matched: true,
type: rule.type,
title: rule.title,
explanation: rule.explanation,
causes: rule.causes,
fix: rule.fix
};
}
}
return {
matched: false,
title: "🔍 Allgemeiner Python-Fehler",
explanation: "Lies den Traceback immer von ganz unten nach oben: In der allerletzten Zeile nennt Python die genaue Fehlerart und Zeilennummer.",
causes: ["Syntax- oder Logikfehler in deinem Code."],
fix: "Überprüfe die in der letzten Zeile genannte Zeilennummer in deiner aufgabe.py Datei."
};
};
// Widget rendern, falls Container vorhanden
document.addEventListener("DOMContentLoaded", () => {
const interpreterEl = document.getElementById("error-interpreter-widget");
if (!interpreterEl) return;
interpreterEl.innerHTML = `
<div class="card" style="border-left: 5px solid var(--secondary);">
<h2>🩺 Interaktiver Python-Fehler-Dolmetscher</h2>
<p>Du verstehst eine Fehlermeldung aus dem Terminal nicht? Füge sie hier ein und erhalte sofort eine schülerfreundliche deutsche Erklärung mit Reparatur-Anleitung!</p>
<textarea id="traceback-input" placeholder="Füge hier deinen Traceback ein (z.B. TypeError: ... oder IndexError: list index out of range)" style="width: 100%; height: 90px; padding: 12px; font-family: var(--font-mono); font-size: 0.88rem; border-radius: var(--radius-sm); border: 1px solid var(--border-color); margin-bottom: 12px;"></textarea>
<button id="btn-interpret" class="btn" style="background: var(--secondary);">🔍 Fehlermeldung übersetzen</button>
<div id="interpreter-result" style="margin-top: 15px; display: none;"></div>
</div>
`;
const input = document.getElementById("traceback-input");
const btn = document.getElementById("btn-interpret");
const result = document.getElementById("interpreter-result");
btn.addEventListener("click", () => {
const res = window.interpretiereTraceback(input.value);
result.innerHTML = `
<div class="box ${res.matched ? 'box-warning' : 'box-tipp'}">
<div class="box-title">${res.title}</div>
<p><strong>Was bedeutet das?</strong> ${res.explanation}</p>
${res.causes ? `<p><strong>Mögliche Ursachen:</strong></p><ul style="margin-left: 20px;">${res.causes.map(c => `<li>${c}</li>`).join("")}</ul>` : ''}
<div style="margin-top: 10px; padding-top: 10px; border-top: 1px solid rgba(0,0,0,0.1);">
<strong>🔧 So behebst du den Fehler:</strong>
<p style="margin-top: 4px;">${res.fix}</p>
</div>
</div>
`;
result.style.display = "block";
});
});
})();

182
assets/parsons.js Normal file
View file

@ -0,0 +1,182 @@
/**
* 🧩 INTERAKTIVES PARSONS-PUZZLE & PRIMM WIDGET 🧩
* =================================================
* Basiert auf der Forschung von Dale Parsons & Barbara Ericson.
* Ermöglicht Schülerinnen und Schülern das Sortieren von Code-Blöcken
* mit sofortigem Feedback und Gamification-XP.
*/
(function () {
class ParsonsWidget {
constructor(container) {
this.container = container;
this.puzzleId = container.dataset.puzzleId || "puzzle_" + Math.random().toString(36).substr(2, 9);
this.initialLines = JSON.parse(container.dataset.lines || "[]");
this.solution = JSON.parse(container.dataset.solution || "[]");
this.explanation = container.dataset.explanation || "Sehr gut! Du hast die Logik fehlerfrei aufgebaut.";
this.xpReward = parseInt(container.dataset.xp || "25", 10);
this.currentLines = [...this.initialLines];
this.isSolved = this.loadSolvedState();
this.render();
}
loadSolvedState() {
try {
const solved = JSON.parse(localStorage.getItem("parsons_solved") || "{}");
return !!solved[this.puzzleId];
} catch (e) {
return false;
}
}
saveSolvedState() {
try {
const solved = JSON.parse(localStorage.getItem("parsons_solved") || "{}");
solved[this.puzzleId] = true;
localStorage.setItem("parsons_solved", JSON.stringify(solved));
// Gamification XP Bonus gutschreiben
if (window.GAMIFICATION_DATA) {
window.GAMIFICATION_DATA.xp = (window.GAMIFICATION_DATA.xp || 0) + this.xpReward;
if (typeof renderGamificationHUD === "function") {
renderGamificationHUD();
}
}
} catch (e) {
console.error("Fehler beim Speichern des Puzzle-Status:", e);
}
}
render() {
this.container.classList.add("parsons-box");
this.container.innerHTML = `
<div class="parsons-header">
<div class="parsons-title">
<span>🧩 Code-Puzzle (Parsons Problem)</span>
<span class="parsons-badge">${this.isSolved ? '✅ Gelöst (+'+this.xpReward+' XP)' : '⭐ +'+this.xpReward+' XP'}</span>
</div>
<p class="parsons-instruction">
Bringe die Code-Blöcke in die richtige logische Reihenfolge, bevor du selbst programmierst:
</p>
</div>
<div class="parsons-board" id="board-${this.puzzleId}">
${this.currentLines.map((line, idx) => `
<div class="parsons-line" draggable="true" data-index="${idx}">
<div class="parsons-handle">☰</div>
<div class="parsons-code"><code>${this.escapeHtml(line)}</code></div>
<div class="parsons-actions">
<button type="button" class="btn-parsons-move btn-up" data-dir="-1" title="Nach oben" ${idx === 0 ? 'disabled' : ''}>▲</button>
<button type="button" class="btn-parsons-move btn-down" data-dir="1" title="Nach unten" ${idx === this.currentLines.length - 1 ? 'disabled' : ''}>▼</button>
</div>
</div>
`).join("")}
</div>
<div class="parsons-controls">
<button type="button" class="btn btn-parsons-check" id="check-${this.puzzleId}">
${this.isSolved ? '🔄 Erneut prüfen' : '✨ Lösung prüfen'}
</button>
<button type="button" class="btn btn-secondary btn-parsons-hint" id="hint-${this.puzzleId}">
💡 Ersten Block verraten
</button>
</div>
<div class="parsons-feedback" id="feedback-${this.puzzleId}" style="${this.isSolved ? 'display: block;' : 'display: none;'}">
${this.isSolved ? `<div class="box box-success"><div class="box-title">🎉 Perfekt gelöst!</div>${this.explanation}</div>` : ''}
</div>
`;
this.attachEvents();
}
escapeHtml(text) {
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
}
attachEvents() {
const board = document.getElementById(`board-${this.puzzleId}`);
const checkBtn = document.getElementById(`check-${this.puzzleId}`);
const hintBtn = document.getElementById(`hint-${this.puzzleId}`);
// Up / Down Buttons
board.querySelectorAll(".btn-parsons-move").forEach(btn => {
btn.addEventListener("click", (e) => {
const lineEl = e.target.closest(".parsons-line");
const idx = parseInt(lineEl.dataset.index, 10);
const dir = parseInt(e.target.dataset.dir, 10);
const targetIdx = idx + dir;
if (targetIdx >= 0 && targetIdx < this.currentLines.length) {
const temp = this.currentLines[idx];
this.currentLines[idx] = this.currentLines[targetIdx];
this.currentLines[targetIdx] = temp;
this.render();
}
});
});
// Check Button
checkBtn.addEventListener("click", () => this.checkSolution());
// Hint Button
hintBtn.addEventListener("click", () => {
const correctFirst = this.solution[0];
const currentFirstIdx = this.currentLines.indexOf(correctFirst);
if (currentFirstIdx > 0) {
this.currentLines.splice(currentFirstIdx, 1);
this.currentLines.unshift(correctFirst);
this.render();
const feedback = document.getElementById(`feedback-${this.puzzleId}`);
feedback.innerHTML = `<div class="box box-tipp"><div class="box-title">💡 Tipp:</div>Der erste Block <code>${this.escapeHtml(correctFirst)}</code> wurde an den Anfang gesetzt!</div>`;
feedback.style.display = "block";
}
});
}
checkSolution() {
const feedback = document.getElementById(`feedback-${this.puzzleId}`);
let correct = true;
for (let i = 0; i < this.solution.length; i++) {
if (this.currentLines[i] !== this.solution[i]) {
correct = false;
break;
}
}
if (correct) {
feedback.innerHTML = `
<div class="box box-success" style="animation: fadeIn 0.3s ease;">
<div class="box-title">🎉 Bravo! Alle Blöcke sind in der perfekten Reihenfolge!</div>
<p>${this.explanation}</p>
<p style="margin-top: 8px; font-weight: bold; color: var(--success);">
⭐ Du hast <strong>+${this.xpReward} XP</strong> für deinen Entwickler-Pass freigeschaltet!
</p>
</div>
`;
feedback.style.display = "block";
this.saveSolvedState();
this.isSolved = true;
} else {
feedback.innerHTML = `
<div class="box box-warning" style="animation: fadeIn 0.3s ease;">
<div class="box-title">🤔 Noch nicht ganz...</div>
<p>Einige Zeilen stehen noch an der falschen Stelle. Überlege Schritt für Schritt: Was muss zuerst initialisiert oder berechnet werden, bevor der nächste Schritt ausgeführt werden kann?</p>
</div>
`;
feedback.style.display = "block";
}
}
}
// Initialisiere alle Puzzle-Widgets auf der Seite
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll(".parsons-puzzle").forEach(el => {
new ParsonsWidget(el);
});
});
})();

View file

@ -283,7 +283,7 @@ kbd {
margin: 0 2px; margin: 0 2px;
} }
/* Extension Cards (for VS Code Guide) */ /* Extension Cards */
.extension-grid { .extension-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
@ -402,7 +402,7 @@ summary:hover {
flex: 1; flex: 1;
} }
/* Grid & Roadmap Cards (for Overview) */ /* Grid & Roadmap Cards */
.roadmap-grid { .roadmap-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
@ -735,6 +735,242 @@ summary:hover {
color: #64748b; color: #64748b;
} }
/* ==========================================================================
PARSONS PUZZLES & PRIMM WIDGETS
========================================================================== */
.parsons-box {
background: #ffffff;
border: 2px solid var(--primary-light);
border-radius: var(--radius-md);
padding: 20px;
margin: 25px 0;
box-shadow: var(--shadow-sm);
}
.parsons-header {
margin-bottom: 15px;
}
.parsons-title {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 1.15rem;
font-weight: 800;
color: var(--primary);
margin-bottom: 6px;
}
.parsons-badge {
background: var(--warning-light);
color: #b45309;
padding: 3px 10px;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 700;
}
.parsons-instruction {
font-size: 0.92rem;
color: var(--text-muted);
margin: 0;
}
.parsons-board {
display: flex;
flex-direction: column;
gap: 8px;
margin: 15px 0;
}
.parsons-line {
display: flex;
align-items: center;
gap: 12px;
background: #f8fafc;
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
padding: 10px 14px;
cursor: grab;
transition: all 0.2s;
}
.parsons-line:hover {
border-color: var(--primary);
background: var(--primary-light);
}
.parsons-handle {
color: var(--text-muted);
font-size: 1.1rem;
cursor: grab;
user-select: none;
}
.parsons-code {
flex: 1;
}
.parsons-code code {
background: transparent;
color: var(--text-main);
font-size: 0.95rem;
padding: 0;
}
.parsons-actions {
display: flex;
gap: 4px;
}
.btn-parsons-move {
background: #ffffff;
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 2px 8px;
font-size: 0.75rem;
cursor: pointer;
color: var(--text-muted);
}
.btn-parsons-move:hover:not(:disabled) {
background: var(--primary);
color: white;
border-color: var(--primary);
}
.btn-parsons-move:disabled {
opacity: 0.3;
cursor: not-allowed;
}
.parsons-controls {
display: flex;
gap: 10px;
margin-top: 15px;
flex-wrap: wrap;
}
/* ==========================================================================
OFFICIAL CERTIFICATE MODAL
========================================================================== */
.certificate-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(15, 23, 42, 0.85);
backdrop-filter: blur(4px);
z-index: 9999;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.certificate-container {
background: #ffffff;
border-radius: var(--radius-md);
padding: 20px;
max-width: 750px;
width: 100%;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
}
.certificate-border {
border: 6px double #d97706;
border-radius: var(--radius-sm);
padding: 30px;
text-align: center;
background: #fffdfa;
}
.certificate-badge {
font-size: 0.85rem;
font-weight: 800;
letter-spacing: 2px;
color: #b45309;
margin-bottom: 10px;
}
.certificate-headline {
font-size: 2rem;
color: #1e293b;
margin-bottom: 8px;
}
.certificate-sub {
color: var(--text-muted);
font-size: 0.95rem;
margin-bottom: 12px;
}
.certificate-student-name {
font-size: 1.8rem;
font-weight: 800;
color: var(--primary);
text-decoration: underline;
text-decoration-color: #fbbf24;
text-underline-offset: 6px;
margin: 15px 0;
}
.certificate-text {
color: var(--text-muted);
font-size: 0.95rem;
}
.certificate-track-title {
font-size: 1.35rem;
color: #0f172a;
margin: 8px 0;
}
.certificate-desc {
font-size: 0.88rem;
color: #64748b;
max-width: 550px;
margin: 0 auto 20px auto;
line-height: 1.45;
}
.certificate-meta {
display: flex;
justify-content: space-around;
font-size: 0.9rem;
color: #334155;
margin: 20px 0;
padding: 12px 0;
border-top: 1px solid #e2e8f0;
border-bottom: 1px solid #e2e8f0;
}
.certificate-seal {
font-size: 1rem;
font-weight: 800;
color: #b45309;
letter-spacing: 1px;
}
.certificate-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 20px;
}
@media print {
body * { visibility: hidden; }
.certificate-container, .certificate-container * { visibility: visible; }
.certificate-overlay { position: static; background: transparent; padding: 0; }
.certificate-actions { display: none; }
}
/* Responsive */ /* Responsive */
@media (max-width: 600px) { @media (max-width: 600px) {
header h1 { font-size: 1.6rem; } header h1 { font-size: 1.6rem; }

View file

@ -486,6 +486,32 @@ object has no attribute 'gang'</code></pre>
</div> </div>
</section> </section>
<!-- Interaktiver Traceback-Dolmetscher -->
<section id="error-interpreter-widget"></section>
<!-- Lehrpfad-Zertifikate Card -->
<section class="card" style="border-left: 5px solid #d97706; background: linear-gradient(180deg, #ffffff 0%, #fffdfa 100%);">
<h2>🏆 Offizielle Lehrpfad-Abschlusszertifikate</h2>
<p>
Sobald du einen Lehrpfad oder das gesamte Portal abgeschlossen hast, kannst du dir hier dein persönliches, ausdruckbares <strong>Abschluss-Zertifikat</strong> mit deinen freigeschalteten Leistungsnachweisen generieren!
</p>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; margin-top: 15px;">
<button onclick="window.zeigeZertifikatModal(1)" class="btn" style="background: #1e40af; justify-content: center;">
🌱 Zertifikat Lehrpfad 1
</button>
<button onclick="window.zeigeZertifikatModal(2)" class="btn" style="background: #4338ca; justify-content: center;">
🏗️ Zertifikat Lehrpfad 2
</button>
<button onclick="window.zeigeZertifikatModal(3)" class="btn" style="background: #15803d; justify-content: center;">
🚀 Zertifikat Lehrpfad 3
</button>
<button onclick="window.zeigeZertifikatModal('master')" class="btn" style="background: #b45309; justify-content: center; font-weight: bold;">
👑 Master-Zertifikat
</button>
</div>
</section>
<!-- FAQ & Troubleshooting Card --> <!-- FAQ & Troubleshooting Card -->
<section class="card"> <section class="card">
<h2>🩺 Häufige Fragen & Troubleshooting</h2> <h2>🩺 Häufige Fragen & Troubleshooting</h2>
@ -509,7 +535,7 @@ object has no attribute 'gang'</code></pre>
<details> <details>
<summary>❌ Wie wechsle ich im Terminal zwischen den Ordnern hin und her?</summary> <summary>❌ Wie wechsle ich im Terminal zwischen den Ordnern hin und her?</summary>
<div class="details-content"> <div class="details-content">
<p><strong>In einen Unterordner hinein:</strong> <code>cd g01_erste_schritte_taschenrechner</code></p> <p><strong>In einen Unterordner hinein:</strong> <code>cd lehrpfad_1_grundlagen/01_erste_schritte_taschenrechner</code></p>
<p><strong>Einen Ordner nach oben zurück:</strong> <code>cd ..</code></p> <p><strong>Einen Ordner nach oben zurück:</strong> <code>cd ..</code></p>
<p><strong>Inhalt des aktuellen Ordners auflisten:</strong> <code>ls</code> (oder <code>dir</code> auf Windows)</p> <p><strong>Inhalt des aktuellen Ordners auflisten:</strong> <code>ls</code> (oder <code>dir</code> auf Windows)</p>
</div> </div>
@ -518,8 +544,11 @@ object has no attribute 'gang'</code></pre>
</main> </main>
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Didaktik- & Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="assets/gamification_data.js"></script> <script src="assets/gamification_data.js"></script>
<script src="assets/gamification.js"></script> <script src="assets/gamification.js"></script>
<script src="assets/interpreter.js"></script>
<script src="assets/certificate.js"></script>
<script src="assets/parsons.js"></script>
</body> </body>
</html> </html>

View file

@ -305,5 +305,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -256,5 +256,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -259,5 +259,7 @@ return f"Steckbrief: {name} | Größe: {groesse_m:.2f} m | Gewicht: {gewicht_kg:
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -291,5 +291,7 @@ return False</code></pre>
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -332,5 +332,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -285,5 +285,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -276,5 +276,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -231,5 +231,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -305,5 +305,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -255,5 +255,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -209,5 +209,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -291,5 +291,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -218,5 +218,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -198,5 +198,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -205,5 +205,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -318,5 +318,7 @@ sudo snap install --classic code</code></pre>
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -214,5 +214,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -201,5 +201,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -179,5 +179,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -215,5 +215,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -280,5 +280,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -299,5 +299,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -359,5 +359,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -392,5 +392,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -259,5 +259,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -247,5 +247,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -352,5 +352,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -316,5 +316,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>

View file

@ -327,5 +327,7 @@
<!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) --> <!-- Lokale Gamification-Scripts (funktionieren auch offline per Doppelklick via file://) -->
<script src="../../assets/gamification_data.js"></script> <script src="../../assets/gamification_data.js"></script>
<script src="../../assets/gamification.js"></script> <script src="../../assets/gamification.js"></script>
<script src="../../assets/parsons.js"></script>
<script src="../../assets/interpreter.js"></script>
</body> </body>
</html> </html>