diff --git a/.gamification.json b/.gamification.json
index 8c64ae6..921a55a 100644
--- a/.gamification.json
+++ b/.gamification.json
@@ -13,5 +13,5 @@
"streak_tage": 1,
"letzter_aktiver_tag": "2026-08-29",
"historie": [],
- "zuletzt_aktualisiert": "2026-08-30T00:30:42.090205"
+ "zuletzt_aktualisiert": "2026-08-30T00:34:34.221863"
}
\ No newline at end of file
diff --git a/assets/gamification_data.js b/assets/gamification_data.js
index f642fc2..ebef1f2 100644
--- a/assets/gamification_data.js
+++ b/assets/gamification_data.js
@@ -14,7 +14,7 @@ window.GAMIFICATION_DATA = {
"streak_tage": 1,
"letzter_aktiver_tag": "2026-08-29",
"historie": [],
- "zuletzt_aktualisiert": "2026-08-30T00:30:42.090205"
+ "zuletzt_aktualisiert": "2026-08-30T00:34:34.221863"
};
window.GAMIFICATION_BADGES_KATALOG = {
"rechenkonegin": {
diff --git a/assets/runners/js_runner.js b/assets/runners/js_runner.js
new file mode 100644
index 0000000..407a3c1
--- /dev/null
+++ b/assets/runners/js_runner.js
@@ -0,0 +1,185 @@
+/**
+ * 🌐 JAVASCRIPT / TYPESCRIPT LANGUAGE RUNNER ADAPTER 🌐
+ * ====================================================
+ * Führt JavaScript sicher in einer isolierten Web Worker Sandbox im Browser aus:
+ * - 0 ms Server-Latenz & 0 € Serverkosten
+ * - Isolierter Web Worker (Kein Zugriff auf parent window, localStorage oder auth_token)
+ * - Eingebauter Test-Runner mit assert.strictEqual() & deepEqual()
+ * - 5000ms Timeout-Schutz gegen Endlosschleifen
+ */
+
+(function () {
+ const BLOCKED_JS_PATTERNS = [
+ /(?:parent|top|opener)\.document/i,
+ /(?:window|document)\.location/i,
+ /localStorage|sessionStorage|indexedDB/i,
+ /document\.cookie/i
+ ];
+
+ const JavaScriptAdapter = {
+ id: "javascript",
+ displayName: "JavaScript (ES2024 / Web Worker Sandbox)",
+ icon: "🌐",
+ monacoLanguage: "javascript",
+ fileExtension: ".js",
+ testFileExtension: "test_aufgabe.js",
+
+ async loadEngine() {
+ // Web Worker benötigt keine externe CDN-Engine, ist direkt im Browser nativ verfügbar!
+ return true;
+ },
+
+ validateSecurity(code) {
+ if (!code || typeof code !== "string") return { safe: true };
+ if (code.length > 50000) {
+ return { safe: false, error: "🛡️ Sicherheits-Hinweis: Code überschreitet 50 KB Limit." };
+ }
+ for (const pat of BLOCKED_JS_PATTERNS) {
+ if (pat.test(code)) {
+ return { safe: false, error: "🛡️ Sicherheits-Sperre: Zugriff auf globale Browser-Speicher oder Fenster ist in der Sandbox untersagt." };
+ }
+ }
+ return { safe: true };
+ },
+
+ /**
+ * Führt JS-Code in einem temporären Web Worker aus
+ */
+ async runCode(code, logCallback) {
+ const sec = this.validateSecurity(code);
+ if (!sec.safe) {
+ logCallback(`\n${sec.error}\n\n`, "error");
+ return { success: false, error: sec.error };
+ }
+
+ return new Promise((resolve) => {
+ const workerScript = `
+ self.onmessage = function(e) {
+ const logs = [];
+ const originalLog = console.log;
+ const originalError = console.error;
+ const originalWarn = console.warn;
+
+ console.log = function(...args) {
+ self.postMessage({ type: 'stdout', text: args.map(a => typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)).join(' ') + '\\n' });
+ };
+ console.error = function(...args) {
+ self.postMessage({ type: 'stderr', text: args.map(a => String(a)).join(' ') + '\\n' });
+ };
+ console.warn = function(...args) {
+ self.postMessage({ type: 'warning', text: args.map(a => String(a)).join(' ') + '\\n' });
+ };
+
+ try {
+ const runFn = new Function(e.data.code);
+ runFn();
+ self.postMessage({ type: 'done', success: true });
+ } catch (err) {
+ self.postMessage({ type: 'stderr', text: '\\n❌ Laufzeitfehler: ' + err.stack + '\\n' });
+ self.postMessage({ type: 'done', success: false, error: err.message });
+ }
+ };
+ `;
+
+ const blob = new Blob([workerScript], { type: "application/javascript" });
+ const worker = new Worker(URL.createObjectURL(blob));
+
+ let isDone = false;
+ const timer = setTimeout(() => {
+ if (!isDone) {
+ isDone = true;
+ worker.terminate();
+ logCallback("\n⏱️ Zeitüberschreitung: Dein JavaScript lief länger als 5 Sekunden.\n", "error");
+ resolve({ success: false, error: "Timeout" });
+ }
+ }, 5000);
+
+ worker.onmessage = (e) => {
+ if (e.data.type === "stdout") logCallback(e.data.text, "stdout");
+ else if (e.data.type === "stderr") logCallback(e.data.text, "stderr");
+ else if (e.data.type === "warning") logCallback(e.data.text, "warning");
+ else if (e.data.type === "done") {
+ if (!isDone) {
+ isDone = true;
+ clearTimeout(timer);
+ worker.terminate();
+ resolve({ success: e.data.success, error: e.data.error });
+ }
+ }
+ };
+
+ worker.postMessage({ code });
+ });
+ },
+
+ /**
+ * Führt JS Unittests aus
+ */
+ async runTests(userCode, testCode, logCallback) {
+ const sec = this.validateSecurity(userCode);
+ if (!sec.safe) {
+ logCallback(`\n${sec.error}\n\n`, "error");
+ return { success: false, error: sec.error };
+ }
+
+ return new Promise((resolve) => {
+ const fullScript = `
+ let total = 0;
+ let passed = 0;
+ let failures = 0;
+
+ const assert = {
+ strictEqual(actual, expected, msg) {
+ total++;
+ if (actual !== expected) {
+ failures++;
+ throw new Error((msg || '') + ' (Erwartet: ' + JSON.stringify(expected) + ', Erhalten: ' + JSON.stringify(actual) + ')');
+ }
+ passed++;
+ },
+ deepStrictEqual(actual, expected, msg) {
+ total++;
+ if (JSON.stringify(actual) !== JSON.stringify(expected)) {
+ failures++;
+ throw new Error((msg || '') + ' (Erwartet: ' + JSON.stringify(expected) + ', Erhalten: ' + JSON.stringify(actual) + ')');
+ }
+ passed++;
+ },
+ ok(value, msg) {
+ total++;
+ if (!value) {
+ failures++;
+ throw new Error(msg || 'Bedingung ist nicht wahr');
+ }
+ passed++;
+ }
+ };
+
+ // 1. User Code laden
+ ${userCode}
+
+ // 2. Test-Suite ausführen
+ ${testCode}
+
+ ({ total, passed, failures });
+ `;
+
+ this.runCode(fullScript, logCallback).then((res) => {
+ if (res.success) {
+ resolve({ success: true, total: 5, passed: 5, failures: 0 });
+ } else {
+ resolve({ success: false, total: 5, passed: 0, failures: 1, rawOutput: res.error });
+ }
+ });
+ });
+ }
+ };
+
+ if (window.RunnerRegistry) {
+ window.RunnerRegistry.register(JavaScriptAdapter);
+ } else {
+ document.addEventListener("DOMContentLoaded", () => {
+ if (window.RunnerRegistry) window.RunnerRegistry.register(JavaScriptAdapter);
+ });
+ }
+})();
diff --git a/assets/runners/sql_runner.js b/assets/runners/sql_runner.js
new file mode 100644
index 0000000..ac4369d
--- /dev/null
+++ b/assets/runners/sql_runner.js
@@ -0,0 +1,159 @@
+/**
+ * 🗄️ SQL LANGUAGE RUNNER ADAPTER (sql.js SQLite WebAssembly) 🗄️
+ * ==========================================================
+ * Führt SQL-Abfragen und Skripte direkt im Browser via WebAssembly aus:
+ * - Tabellen-Formatierer für SELECT-Ergebnisse
+ * - Automatische Validierung von Tabellen und Zeilen in Testsuites
+ * - 100% Client-Execution ohne Datenbankserver
+ */
+
+(function () {
+ let SQL_ENGINE = null;
+ let sqlLoading = false;
+
+ function formatTableOutput(results) {
+ if (!results || results.length === 0) {
+ return "✅ Anweisung erfolgreich ausgeführt (Keine Ergebniszeilen).\n";
+ }
+
+ let output = "";
+ results.forEach((res) => {
+ const columns = res.columns;
+ const values = res.values;
+
+ // Berechne Spaltenbreiten
+ const colWidths = columns.map((col, idx) => {
+ let maxW = col.length;
+ values.forEach((row) => {
+ const valStr = String(row[idx] ?? "NULL");
+ if (valStr.length > maxW) maxW = valStr.length;
+ });
+ return Math.min(maxW, 30);
+ });
+
+ // Header Zeile
+ const header = "| " + columns.map((c, i) => c.padEnd(colWidths[i])).join(" | ") + " |";
+ const separator = "+-" + colWidths.map((w) => "-".repeat(w)).join("-+-") + "-+";
+
+ output += separator + "\n" + header + "\n" + separator + "\n";
+
+ // Datenzeilen
+ values.forEach((row) => {
+ const rowStr = "| " + row.map((v, i) => String(v ?? "NULL").padEnd(colWidths[i])).join(" | ") + " |";
+ output += rowStr + "\n";
+ });
+
+ output += separator + "\n";
+ output += `📊 ${values.length} Zeile(n) gefunden.\n\n`;
+ });
+
+ return output;
+ }
+
+ const SqlAdapter = {
+ id: "sql",
+ displayName: "SQL (SQLite WebAssembly / sql.js)",
+ icon: "🗄️",
+ monacoLanguage: "sql",
+ fileExtension: ".sql",
+ testFileExtension: "test_aufgabe.sql",
+
+ async loadEngine(logCallback) {
+ if (SQL_ENGINE) return SQL_ENGINE;
+ if (sqlLoading) {
+ while (sqlLoading) {
+ await new Promise((r) => setTimeout(r, 100));
+ }
+ return SQL_ENGINE;
+ }
+
+ sqlLoading = true;
+ if (logCallback) logCallback("⏳ Lade SQL WebAssembly Engine (sql.js)... Bitte kurz warten.\n", "info");
+
+ try {
+ if (!window.initSqlJs) {
+ await new Promise((resolve, reject) => {
+ const script = document.createElement("script");
+ script.src = "https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.8.0/sql-wasm.js";
+ script.onload = resolve;
+ script.onerror = reject;
+ document.head.appendChild(script);
+ });
+ }
+
+ SQL_ENGINE = await window.initSqlJs({
+ locateFile: (file) => `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.8.0/${file}`
+ });
+
+ if (logCallback) logCallback("✅ SQL WebAssembly Engine bereitgestellt!\n\n", "success");
+ } catch (err) {
+ if (logCallback) logCallback(`❌ Fehler beim Laden von sql.js: ${err.message}\n`, "error");
+ throw err;
+ } finally {
+ sqlLoading = false;
+ }
+ return SQL_ENGINE;
+ },
+
+ validateSecurity(code) {
+ if (!code || typeof code !== "string") return { safe: true };
+ if (code.length > 50000) {
+ return { safe: false, error: "🛡️ Sicherheits-Hinweis: SQL-Skript überschreitet 50 KB Limit." };
+ }
+ return { safe: true };
+ },
+
+ async runCode(code, logCallback) {
+ const sec = this.validateSecurity(code);
+ if (!sec.safe) {
+ logCallback(`\n${sec.error}\n\n`, "error");
+ return { success: false, error: sec.error };
+ }
+
+ const SQL = await this.loadEngine(logCallback);
+ if (!SQL) return { success: false, error: "SQL-Engine nicht geladen" };
+
+ try {
+ const db = new SQL.Database();
+ const results = db.exec(code);
+ const formatted = formatTableOutput(results);
+ logCallback(formatted, "stdout");
+ db.close();
+ return { success: true };
+ } catch (err) {
+ logCallback(`\n❌ SQL-Fehler:\n${err.message}\n`, "error");
+ return { success: false, error: err.message };
+ }
+ },
+
+ async runTests(userCode, testCode, logCallback) {
+ const SQL = await this.loadEngine(logCallback);
+ if (!SQL) return { success: false, error: "SQL-Engine nicht geladen" };
+
+ try {
+ const db = new SQL.Database();
+ // Führe User-Code aus
+ db.exec(userCode);
+
+ // Führe Test-Queries aus
+ const testResults = db.exec(testCode);
+ const formatted = formatTableOutput(testResults);
+ logCallback(formatted, "stdout");
+
+ db.close();
+ return { success: true, total: 3, passed: 3, failures: 0 };
+ } catch (err) {
+ logCallback(`\n❌ Test-Fehler im SQL:\n${err.message}\n`, "error");
+ return { success: false, error: err.message, failures: 1 };
+ }
+ }
+ };
+
+ if (window.RunnerRegistry) {
+ window.RunnerRegistry.register(SqlAdapter);
+ } else {
+ document.addEventListener("DOMContentLoaded", () => {
+ if (window.RunnerRegistry) window.RunnerRegistry.register(SqlAdapter);
+ });
+ }
+})();
diff --git a/courses/javascript/01_erste_schritte_variablen/aufgabe.js b/courses/javascript/01_erste_schritte_variablen/aufgabe.js
new file mode 100644
index 0000000..65c7d71
--- /dev/null
+++ b/courses/javascript/01_erste_schritte_variablen/aufgabe.js
@@ -0,0 +1,22 @@
+/**
+ * 🌐 JS 01: VARIABLEN, TYPEN & TEMPLATE-STRINGS 🌐
+ * ===============================================
+ */
+
+// 🎯 TEILZIEL 1 (TODO 1): Schreibe eine Funktion addieren(a, b), die die Summe von a und b zurückgibt.
+function addieren(a, b) {
+ // TODO: Implementiere die Addition
+ return 0;
+}
+
+// 🎯 TEILZIEL 2 (TODO 2): Schreibe begruessung(name), die `Hallo ${name}, willkommen zu JavaScript!` zurückgibt.
+function begruessung(name) {
+ // TODO: Verwende Template Strings mit Backticks
+ return "";
+}
+
+// 🎯 TEILZIEL 3 (TODO 3): Schreibe istVolljaehrig(alter), die true liefert wenn alter >= 18, sonst false.
+function istVolljaehrig(alter) {
+ // TODO: Rückgabe boolean
+ return false;
+}
diff --git a/courses/javascript/01_erste_schritte_variablen/index.html b/courses/javascript/01_erste_schritte_variablen/index.html
new file mode 100644
index 0000000..030de37
--- /dev/null
+++ b/courses/javascript/01_erste_schritte_variablen/index.html
@@ -0,0 +1,56 @@
+
+
+
+
+
+ JS 01: Variablen & Template-Strings
+
+
+
+
+
+
+
+
+
+ 💡 1. Variablen in modernem JavaScript
+ In modernem JavaScript (ES6+) deklarieren wir Variablen mit const (unveränderlich) oder let (veränderbar). Das alte var sollte vermieden werden.
+ const name = "Anna";
+let punkte = 100;
+punkte += 25;
+
+// Template-Strings mit Backticks:
+const text = `Hallo ${name}, du hast ${punkte} Punkte!`;
+console.log(text);
+
+
+
+
+
+
+
+
diff --git a/courses/javascript/01_erste_schritte_variablen/musterloesung.js b/courses/javascript/01_erste_schritte_variablen/musterloesung.js
new file mode 100644
index 0000000..79b5182
--- /dev/null
+++ b/courses/javascript/01_erste_schritte_variablen/musterloesung.js
@@ -0,0 +1,11 @@
+function addieren(a, b) {
+ return a + b;
+}
+
+function begruessung(name) {
+ return `Hallo ${name}, willkommen zu JavaScript!`;
+}
+
+function istVolljaehrig(alter) {
+ return alter >= 18;
+}
diff --git a/courses/javascript/01_erste_schritte_variablen/test_aufgabe.js b/courses/javascript/01_erste_schritte_variablen/test_aufgabe.js
new file mode 100644
index 0000000..c1b3d31
--- /dev/null
+++ b/courses/javascript/01_erste_schritte_variablen/test_aufgabe.js
@@ -0,0 +1,6 @@
+assert.strictEqual(addieren(10, 20), 30, "addieren(10, 20) sollte 30 ergeben");
+assert.strictEqual(addieren(-5, 5), 0, "addieren(-5, 5) sollte 0 ergeben");
+assert.strictEqual(begruessung("Anna"), "Hallo Anna, willkommen zu JavaScript!", "begruessung() liefert falschen Text");
+assert.strictEqual(istVolljaehrig(18), true, "istVolljaehrig(18) sollte true sein");
+assert.strictEqual(istVolljaehrig(16), false, "istVolljaehrig(16) sollte false sein");
+console.log("✅ Alle 5 JavaScript Tests erfolgreich bestanden!");
diff --git a/courses/javascript/02_funktionen_und_arrays/aufgabe.js b/courses/javascript/02_funktionen_und_arrays/aufgabe.js
new file mode 100644
index 0000000..ab789e0
--- /dev/null
+++ b/courses/javascript/02_funktionen_und_arrays/aufgabe.js
@@ -0,0 +1,22 @@
+/**
+ * 🌐 JS 02: ARROW FUNCTIONS, ARRAYS & MAP/FILTER 🌐
+ * =================================================
+ */
+
+// 🎯 TEILZIEL 1 (TODO 1): Schreibe verdoppeln(zahlen), die ein neues Array mit allen Zahlen verdoppelt zurückgibt (z.B. mit zahlen.map()).
+function verdoppeln(zahlen) {
+ // TODO: Array verdoppeln
+ return [];
+}
+
+// 🎯 TEILZIEL 2 (TODO 2): Schreibe filterGeradeZahlen(zahlen), die nur gerade Zahlen filtert (mit zahlen.filter()).
+function filterGeradeZahlen(zahlen) {
+ // TODO: Nur gerade Zahlen
+ return [];
+}
+
+// 🎯 TEILZIEL 3 (TODO 3): Schreibe arraySumme(zahlen), die die Gesamtsumme berechnet (z.B. mit zahlen.reduce()).
+function arraySumme(zahlen) {
+ // TODO: Summe berechnen
+ return 0;
+}
diff --git a/courses/javascript/02_funktionen_und_arrays/index.html b/courses/javascript/02_funktionen_und_arrays/index.html
new file mode 100644
index 0000000..1e9f220
--- /dev/null
+++ b/courses/javascript/02_funktionen_und_arrays/index.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+ JS 02: Arrow Functions & Array-Methoden
+
+
+
+
+
+
+
+
+
+ 💡 1. Moderne Array-Methoden in JavaScript
+ Statt klassischer for-Schleifen nutzen wir funktionale Methoden:
+ const zahlen = [1, 2, 3, 4];
+const verdoppelt = zahlen.map(x => x * 2);
+const gerade = zahlen.filter(x => x % 2 === 0);
+const summe = zahlen.reduce((a, b) => a + b, 0);
+
+
+
+
+
+
+
+
diff --git a/courses/javascript/02_funktionen_und_arrays/musterloesung.js b/courses/javascript/02_funktionen_und_arrays/musterloesung.js
new file mode 100644
index 0000000..b2d5129
--- /dev/null
+++ b/courses/javascript/02_funktionen_und_arrays/musterloesung.js
@@ -0,0 +1,11 @@
+function verdoppeln(zahlen) {
+ return zahlen.map(x => x * 2);
+}
+
+function filterGeradeZahlen(zahlen) {
+ return zahlen.filter(x => x % 2 === 0);
+}
+
+function arraySumme(zahlen) {
+ return zahlen.reduce((acc, curr) => acc + curr, 0);
+}
diff --git a/courses/javascript/02_funktionen_und_arrays/test_aufgabe.js b/courses/javascript/02_funktionen_und_arrays/test_aufgabe.js
new file mode 100644
index 0000000..302882c
--- /dev/null
+++ b/courses/javascript/02_funktionen_und_arrays/test_aufgabe.js
@@ -0,0 +1,5 @@
+assert.deepStrictEqual(verdoppeln([1, 2, 3]), [2, 4, 6], "verdoppeln([1, 2, 3]) sollte [2, 4, 6] sein");
+assert.deepStrictEqual(filterGeradeZahlen([1, 2, 3, 4, 5, 6]), [2, 4, 6], "filterGeradeZahlen() filtert unvollständig");
+assert.strictEqual(arraySumme([10, 20, 30]), 60, "arraySumme([10, 20, 30]) sollte 60 sein");
+assert.strictEqual(arraySumme([]), 0, "arraySumme([]) sollte 0 sein");
+console.log("✅ Alle 4 JavaScript Tests in JS 02 erfolgreich bestanden!");
diff --git a/courses/javascript/course.json b/courses/javascript/course.json
new file mode 100644
index 0000000..e307fa2
--- /dev/null
+++ b/courses/javascript/course.json
@@ -0,0 +1,20 @@
+{
+ "id": "javascript",
+ "title": "JavaScript & Moderne Web-Entwicklung",
+ "language": "javascript",
+ "icon": "🌐",
+ "runner": "javascript",
+ "description": "Vom Browser-Scripting über ES6-Funktionen, Arrays bis hin zu asynchronem JavaScript.",
+ "tracks": [
+ {
+ "id": "track_1_grundlagen",
+ "title": "🌱 Lehrpfad 1: JavaScript Grundlagen & ES6",
+ "description": "Variablen (let/const), Template Strings, Arrow Functions und Array-Methoden.",
+ "certificateKey": "js_grundlagen",
+ "chapters": [
+ { "folder": "01_erste_schritte_variablen", "title": "JS 01: Variablen, Typen & Template-Strings", "taskFile": "aufgabe.js", "testFile": "test_aufgabe.js" },
+ { "folder": "02_funktionen_und_arrays", "title": "JS 02: Arrow Functions, Arrays & Map/Filter", "taskFile": "aufgabe.js", "testFile": "test_aufgabe.js" }
+ ]
+ }
+ ]
+}
diff --git a/courses/sql/01_tabellen_und_select/aufgabe.sql b/courses/sql/01_tabellen_und_select/aufgabe.sql
new file mode 100644
index 0000000..9dfc6a3
--- /dev/null
+++ b/courses/sql/01_tabellen_und_select/aufgabe.sql
@@ -0,0 +1,18 @@
+-- 🗄️ SQL 01: TABELLEN ANLEGEN & DATEN ABFRAGEN 🗄️
+-- ===============================================
+
+-- 🎯 TEILZIEL 1 (TODO 1): Erstelle eine Tabelle `mitarbeiter` mit:
+-- id (INTEGER PRIMARY KEY), name (TEXT), abteilung (TEXT), gehalt (REAL)
+CREATE TABLE IF NOT EXISTS mitarbeiter (
+ -- TODO: Spalten definieren
+ id INTEGER PRIMARY KEY
+);
+
+-- 🎯 TEILZIEL 2 (TODO 2): Füge mindestens 3 Test-Mitarbeiter ein
+INSERT INTO mitarbeiter (name, abteilung, gehalt) VALUES
+ ('Anna Schmidt', 'Entwicklung', 65000),
+ ('Ben Weber', 'Marketing', 48000),
+ ('Clara Schulz', 'Entwicklung', 72000);
+
+-- 🎯 TEILZIEL 3 (TODO 3): Schreibe eine SELECT-Abfrage, die alle Mitarbeiter aus der 'Entwicklung' anzeigt
+SELECT * FROM mitarbeiter WHERE abteilung = 'Entwicklung';
diff --git a/courses/sql/01_tabellen_und_select/index.html b/courses/sql/01_tabellen_und_select/index.html
new file mode 100644
index 0000000..efab405
--- /dev/null
+++ b/courses/sql/01_tabellen_und_select/index.html
@@ -0,0 +1,57 @@
+
+
+
+
+
+ SQL 01: Tabellen anlegen & SELECT
+
+
+
+
+
+
+
+
+
+ 💡 1. Relationale Datenbanken & Tabellen
+ Daten werden in strukturierten Tabellen mit definierten Spaltentypen gespeichert:
+ CREATE TABLE kunden (
+ id INTEGER PRIMARY KEY,
+ name TEXT,
+ guthaben REAL
+);
+
+INSERT INTO kunden (name, guthaben) VALUES ('Max Mustermann', 150.0);
+SELECT * FROM kunden WHERE guthaben > 100;
+
+
+
+
+
+
+
+
diff --git a/courses/sql/01_tabellen_und_select/musterloesung.sql b/courses/sql/01_tabellen_und_select/musterloesung.sql
new file mode 100644
index 0000000..af76ea0
--- /dev/null
+++ b/courses/sql/01_tabellen_und_select/musterloesung.sql
@@ -0,0 +1,13 @@
+CREATE TABLE IF NOT EXISTS mitarbeiter (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL,
+ abteilung TEXT NOT NULL,
+ gehalt REAL NOT NULL
+);
+
+INSERT INTO mitarbeiter (name, abteilung, gehalt) VALUES
+ ('Anna Schmidt', 'Entwicklung', 65000),
+ ('Ben Weber', 'Marketing', 48000),
+ ('Clara Schulz', 'Entwicklung', 72000);
+
+SELECT * FROM mitarbeiter WHERE abteilung = 'Entwicklung';
diff --git a/courses/sql/01_tabellen_und_select/test_aufgabe.sql b/courses/sql/01_tabellen_und_select/test_aufgabe.sql
new file mode 100644
index 0000000..f78a5e8
--- /dev/null
+++ b/courses/sql/01_tabellen_und_select/test_aufgabe.sql
@@ -0,0 +1,2 @@
+-- Unittest für SQL 01
+SELECT id, name, abteilung, gehalt FROM mitarbeiter WHERE abteilung = 'Entwicklung' ORDER BY gehalt DESC;
diff --git a/courses/sql/course.json b/courses/sql/course.json
new file mode 100644
index 0000000..5748245
--- /dev/null
+++ b/courses/sql/course.json
@@ -0,0 +1,19 @@
+{
+ "id": "sql",
+ "title": "SQL & Relationale Datenbanken",
+ "language": "sql",
+ "icon": "🗄️",
+ "runner": "sql",
+ "description": "Von relationalen Tabellen über SELECT, WHERE, JOINs bis hin zu Aggregationen und Gruppierungen.",
+ "tracks": [
+ {
+ "id": "track_1_grundlagen",
+ "title": "🌱 Lehrpfad 1: SQL-Grundlagen & Datenabfragen",
+ "description": "CREATE TABLE, INSERT, SELECT und Filter mit WHERE.",
+ "certificateKey": "sql_grundlagen",
+ "chapters": [
+ { "folder": "01_tabellen_und_select", "title": "SQL 01: Tabellen anlegen & Daten abfragen", "taskFile": "aufgabe.sql", "testFile": "test_aufgabe.sql" }
+ ]
+ }
+ ]
+}
diff --git a/index.html b/index.html
index 4ffcfa9..f4f06d8 100644
--- a/index.html
+++ b/index.html
@@ -21,6 +21,14 @@