# LMS Kurs-Schema — Anleitung für KI-Agenten (JSON-Import)

Erstelle Kurse für ein deutschsprachiges LMS als JSON-Datei.
Der Kurs kann direkt über **Meine Kurse → Kurs importieren** in das LMS geladen werden.

---

## Dateiformat

**Option A — Reines Kurs-JSON** (empfohlen):
```json
{
  "id": "kurs-abc123",
  "title": "...",
  "lessons": [...]
}
```

**Option B — Export-Bundle** (mit eingebetteten Medien):
```json
{
  "_lms_export": 1,
  "course": { ... },
  "media": { "data/uploads/abc.png": "<base64>" }
}
```

---

## Gesamtstruktur

```
Kurs
└── lessons[]
    ├── sections[]        ← Erklärungsinhalt (Abschnitte, sequenziell)
    │   └── blocks[]      ← Inhaltsblöcke
    ├── tasks[]           ← Aufgaben für die gesamte Lektion (nicht pro Abschnitt!)
    └── quiz[]            ← Abschluss-Quiz
```

### ⚠️ Wichtig: Sections und Tasks sind getrennte Tabs

Eine Lektion hat zwei Tabs für Schüler:
1. **Abschnitte** — `sections[]` werden **nacheinander** durchgearbeitet (Schüler klickt sich mit „Weiter" durch Abschnitt 1 → 2 → 3 usw.)
2. **Aufgaben-Tab** — Alle `tasks[]` erscheinen **gemeinsam auf einer Seite** — es gibt kein „Tasks pro Abschnitt".

**Konsequenz für die Kursgestaltung:**

Wenn eine Lektion mehrere Abschnitte hat (z.B. Phase 1: Theorie, Phase 2: Beispiel, Phase 3: Praxis), müssen **alle** Aufgaben aller Phasen in dem gemeinsamen `tasks[]`-Array stehen. Damit Schüler erkennen, zu welchem Abschnitt eine Aufgabe gehört, nutze `text-block` als Trenner und `info-block` für Hinweise:

```json
"tasks": [
  {
    "type": "text-block",
    "id": "t-h1",
    "content": "<strong>📘 Aufgaben zu Abschnitt 1 — Theorie</strong>"
  },
  { "type": "freitext", "id": "t-a1", "title": "...", "bit": 1 },
  {
    "type": "info-block",
    "id": "t-hint",
    "content": "Arbeite jetzt Abschnitt 2 durch, bevor du hier weitermachst.",
    "style": "warning"
  },
  {
    "type": "text-block",
    "id": "t-h2",
    "content": "<strong>📗 Aufgaben zu Abschnitt 2 — Praxis</strong>"
  },
  { "type": "mc", "id": "t-a2", "title": "...", "opts": ["..."], "ok": 0, "explain": "...", "bit": 2 }
]
```

**Regel:** Bei Lektionen mit 2+ Abschnitten immer `text-block`-Trenner zwischen die Aufgabengruppen setzen.

---

## Kurs (oberste Ebene)

```json
{
  "id": "kurs-abc123",
  "title": "Python Grundkurs",
  "subtitle": "Für Einsteiger ab Klasse 7",
  "desc": "In diesem Kurs lernst du die Grundlagen von Python.",
  "icon": "🐍",
  "color": "#4f46e5",
  "tags": ["Python", "Anfänger"],
  "active": false,
  "boardEnabled": true,
  "lessons": []
}
```

| Feld | Pflicht | Typ | Limit | Beschreibung |
|---|---|---|---|---|
| `id` | ja | string | — | Wird beim Import überschrieben, trotzdem angeben |
| `title` | ja | string | 100 | Kursname |
| `subtitle` | nein | string | 200 | Untertitel |
| `desc` | nein | string | 500 | Kurzbeschreibung |
| `icon` | nein | string | 10 | Emoji (Standard: 📖) |
| `color` | nein | string | — | Hex-Farbe, z.B. `#4f46e5` |
| `tags` | nein | array | max. 10 | Schlagwörter, je max. 30 Zeichen |
| `active` | nein | bool | — | Beim Import immer `false` |
| `boardEnabled` | nein | bool | — | Diskussions-Pinnwand (Standard: `true`) |
| `lessons` | ja | array | — | Array von Lektionen |

---

## Lektion

```json
{
  "key": "l-intro",
  "title": "Variablen und Datentypen",
  "desc": "Du lernst, wie man Werte speichert.",
  "icon": "📦",
  "tutorEnabled": false,
  "tutorPrompt": "",
  "sections": [],
  "tasks": [],
  "quiz": []
}
```

| Feld | Pflicht | Typ | Limit | Beschreibung |
|---|---|---|---|---|
| `key` | ja | string | — | Eindeutig, nur `[a-z0-9-]`, z.B. `l-vars` |
| `title` | ja | string | 200 | Lektionstitel |
| `desc` | nein | string | 500 | Kurzbeschreibung der Lektion |
| `icon` | nein | string | 10 | Emoji (Standard: 📖) |
| `tutorEnabled` | nein | bool | — | KI-Tutor bei Aufgaben aktiv (braucht LLM-Config) |
| `tutorPrompt` | nein | string | 2000 | Eigener System-Prompt für den KI-Tutor |
| `sections` | ja | array | — | Abschnitte mit Erklärungsinhalt |
| `tasks` | ja | array | — | Aufgaben |
| `quiz` | ja | array | — | Abschluss-Quiz |

---

## Abschnitte (sections)

Jede Lektion hat ein `sections`-Array. Die Anzahl steuert die Navigation:

- **1 Abschnitt** → kein Stepper, Inhalt direkt sichtbar
- **2+ Abschnitte** → Weiter/Zurück-Navigation mit Dots

```json
{
  "title": "Was ist eine Variable?",
  "blocks": []
}
```

| Feld | Pflicht | Typ | Limit | Beschreibung |
|---|---|---|---|---|
| `title` | nein | string | 200 | Abschnittstitel (leer = kein Titel) |
| `blocks` | ja | array | — | Inhaltsblöcke (s.u.) |

---

## Inhaltsblöcke (blocks)

### text
Fließtext mit einfachem HTML.
```json
{ "type": "text", "html": "<p>Text mit <b>fett</b> und <code>Code</code>.</p>" }
```
Erlaubte Tags: `<p>` `<b>` `<i>` `<u>` `<code>` `<ul>` `<ol>` `<li>` `<br>` `<a>`

---

### heading
Überschrift innerhalb des Abschnitts.
```json
{ "type": "heading", "text": "Wichtiger Abschnitt", "level": 2 }
```
`level`: `2` (groß) oder `3` (klein)

---

### highlight
Hervorgehobener Hinweis-Block (visuell abgesetzt).
```json
{ "type": "highlight", "text": "Merke: Variablennamen beginnen nie mit einer Zahl." }
```

---

### info-box
Tipp- oder Info-Box.
```json
{ "type": "info-box", "text": "Tipp: Nutze sprechende Variablennamen wie alter statt a." }
```

---

### image
Bild (URL oder hochgeladen).
```json
{
  "type": "image",
  "url": "https://example.com/bild.png",
  "alt": "Diagramm: Variablen im Speicher",
  "showCaption": true
}
```
`showCaption`: `true` zeigt `alt` als Bildunterschrift

---

### video
YouTube, Vimeo oder direkter MP4-Link. Optional mit Datei zum Herunterladen.
```json
{
  "type": "video",
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "title": "Einführung in Variablen",
  "file": "https://example.com/arbeitsblatt.pdf",
  "fileLabel": "Arbeitsblatt herunterladen"
}
```

| Feld | Beschreibung |
|---|---|
| `url` | YouTube (`youtube.com/watch?v=…` oder `youtu.be/…`), Vimeo (`vimeo.com/123456`), oder direkter MP4-Link |
| `title` | Optionaler Videotitel |
| `file` | Optionale Datei-URL für Download-Button |
| `fileLabel` | Beschriftung des Download-Buttons (Standard: Dateiname) |

---

### audio
Audiodatei mit optionalem Titel.
```json
{ "type": "audio", "url": "https://example.com/aussprache.mp3", "title": "Aussprache: Variable" }
```

---

### code-explain
Code-Block mit klickbaren Annotationen (links Code, rechts Erklärungen).
```json
{
  "type": "code-explain",
  "label": "Beispielskript",
  "code": "name = input('Dein Name: ')\nalter = int(input('Alter: '))\nprint('Hallo', name, '! Du bist', alter, 'Jahre alt.')",
  "annotations": [
    {
      "lines": [0],
      "label": "1 – Eingabe",
      "text": "<code>input()</code> liest Text vom Benutzer ein und gibt ihn als String zurück."
    },
    {
      "lines": [1],
      "label": "2 – Umwandlung",
      "text": "<code>int()</code> wandelt den String in eine ganze Zahl um."
    },
    {
      "lines": [2],
      "label": "3 – Ausgabe",
      "text": "<code>print()</code> gibt mehrere Werte mit Leerzeichen getrennt aus."
    }
  ]
}
```
- `lines`: Array von **0-basierten** Zeilennummern, die beim Klick hervorgehoben werden
- `label`: Kurztitel der Annotation (max. 200 Zeichen)
- `text`: Erklärungstext, darf einfaches HTML enthalten (`<b>`, `<i>`, `<code>`)

---

### poll
Abstimmung — Ergebnis anonym, einmal abstimmen, Lehrer kann zurücksetzen.

**Multiple Choice:**
```json
{
  "type": "poll",
  "id": "poll-abc123",
  "pollType": "mc",
  "question": "Was gibt print('Hallo') aus?",
  "opts": ["Hallo", "print", "Fehler", "Nichts"]
}
```
`opts`: 2–6 Optionen, je max. 100 Zeichen

**Skala:**
```json
{
  "type": "poll",
  "id": "poll-xyz789",
  "pollType": "scale",
  "question": "Wie verständlich war die Erklärung?",
  "scaleMax": 5,
  "scaleLabels": ["Gar nicht", "Sehr gut"]
}
```
`scaleMax`: 2–10 | `scaleLabels`: [links, rechts], optional

> **Wichtig:** `id` muss kursübergreifend eindeutig sein, z.B. `poll-` + 6 zufällige alphanumerische Zeichen.

---

### ai-chat
KI-Chat-Widget (braucht LLM-Konfiguration im Kurs).
```json
{
  "type": "ai-chat",
  "placeholder": "Stell mir eine Frage zur Lektion…",
  "systemprompt": "Gib nur Hinweise, keine fertigen Lösungen."
}
```
`systemprompt` leer → Kurs-System-Prompt wird verwendet

---

### link
Klickbares Link-Element mit Icon und Beschriftung — modernes Card-Design, öffnet in neuem Tab.
```json
{
  "type": "link",
  "url": "https://docs.python.org/3/",
  "label": "Offizielle Python-Dokumentation",
  "icon": "🐍"
}
```
| Feld | Pflicht | Beschreibung |
|---|---|---|
| `url` | ja | Vollständige URL inkl. `https://` |
| `label` | nein | Beschriftung (Standard: URL) |
| `icon` | nein | Emoji-Icon (Standard: 🔗) |

---

### scratch
Eingebetteter TurboWarp/Scratch-Editor.
```json
{
  "type": "scratch",
  "projectId": "482162841",
  "starterUrl": "",
  "height": 500,
  "allowedCategories": ["Bewegung", "Aussehen", "Ereignisse"]
}
```
`projectId` leer → leerer Editor | `starterUrl` überschreibt `projectId` | `height`: 300–900

---

## Aufgaben (tasks)

Interaktive Task-Typen haben diese gemeinsamen Felder:

| Feld | Pflicht | Beschreibung |
|---|---|---|
| `id` | ja | Eindeutig, z.B. `t-` + 6 zufällige Zeichen |
| `type` | ja | Typ (s.u.) |
| `title` | ja | Aufgabentitel, max. 200 Zeichen |
| `desc` | nein | Aufgabenbeschreibung, max. 1000 Zeichen (Plaintext) |
| `image` | nein | Bild-URL zur Aufgabe |
| `bit` | ja | Fortschritts-Bit: aufsteigend `1, 2, 3, …` innerhalb der Lektion |

**Hinweis:** `text-block` und `info-block` sind **Anzeigeblöcke** ohne Interaktion. Kein `bit` nötig. Können frei zwischen interaktive Tasks gemischt werden.

---

### text-block
Reiner Anzeigetext (HTML erlaubt). Kein Fortschritts-Bit, kein Schüler-Input.
```json
{
  "type": "text-block",
  "id": "t-txt001",
  "content": "<p>Lies die folgende Tabelle und beantworte danach die Fragen.</p>"
}
```

---

### info-block
Farbiger Infokasten. Kein Fortschritts-Bit.
```json
{
  "type": "info-block",
  "id": "t-inf001",
  "content": "Tipp: Verwende <code>print()</code> um Werte auszugeben.",
  "style": "info"
}
```
`style`: `"info"` (Standard) | `"warning"` (gelb) | `"success"` (grün)

---

### freitext
Freitext-Antwort — Schüler kann tippen oder mit Handschrift zeichnen.
```json
{
  "type": "freitext",
  "id": "t-abc001",
  "title": "Erkläre in eigenen Worten",
  "desc": "Was ist der Unterschied zwischen int und float?",
  "placeholder": "Deine Antwort hier…",
  "bit": 1
}
```

---

### mc (Multiple Choice)
Eine richtige Antwort aus vier Optionen.
```json
{
  "type": "mc",
  "id": "t-abc002",
  "title": "Datentypen",
  "desc": "Welchen Datentyp hat der Wert 3.14?",
  "opts": ["int", "float", "str", "bool"],
  "ok": 1,
  "explain": "3.14 ist eine Dezimalzahl, also float.",
  "bit": 2
}
```
`ok`: Index der richtigen Antwort (0-basiert) | `opts`: genau 4 Einträge

---

### tf (Richtig/Falsch)
Wahr-oder-Falsch-Aussage.
```json
{
  "type": "tf",
  "id": "t-abc003",
  "title": "Aussage prüfen",
  "desc": "In Python muss man Variablen vor der Nutzung deklarieren.",
  "ok": false,
  "explain": "Python ist dynamisch typisiert — keine Deklaration nötig.",
  "bit": 3
}
```

---

### checkbox (Mehrfachauswahl)
Mehrere richtige Antworten möglich.
```json
{
  "type": "checkbox",
  "id": "t-abc004",
  "title": "Gültige Datentypen",
  "desc": "Welche sind eingebaute Python-Datentypen?",
  "opts": ["int", "float", "decimal", "str", "array"],
  "ok": [0, 1, 3],
  "explain": "decimal und array sind keine eingebauten Typen.",
  "bit": 4
}
```
`ok`: Array der korrekten Indizes (0-basiert) | `opts`: 2–8 Einträge

---

### code (Python-Aufgabe)
Code-Editor mit optionaler automatischer Auswertung.
```json
{
  "type": "code",
  "id": "t-abc005",
  "title": "Summe berechnen",
  "desc": "Schreibe eine Funktion add(a, b), die die Summe zurückgibt.",
  "starter": "def add(a, b):\n    # Dein Code hier\n    pass",
  "expectedOutput": "",
  "expectedHint": "Nutze den + Operator und return.",
  "assertions": [
    { "expr": "add(2, 3) == 5", "msg": "add(2, 3) soll 5 ergeben" },
    { "expr": "add(-1, 1) == 0", "msg": "add(-1, 1) soll 0 ergeben" }
  ],
  "bit": 5
}
```

| Feld | Beschreibung |
|---|---|
| `starter` | Vorgegebener Startercode (Zeilenumbrüche als `\n`) |
| `expectedOutput` | Erwarteter `print()`-Output (leer = immer manuell abhakbar) |
| `expectedHint` | Hinweis bei falscher Antwort |
| `assertions` | Python-Ausdrücke die `True` ergeben müssen |

---

### scratch (Scratch-Aufgabe)
TurboWarp-Aufgabe mit eingebettetem Editor.
```json
{
  "type": "scratch",
  "id": "t-abc006",
  "title": "Figur bewegen",
  "desc": "Lass die Figur bei Tastendruck 10 Schritte gehen.",
  "projectId": "",
  "starterUrl": "",
  "height": 500,
  "allowedCategories": ["Bewegung", "Ereignisse"],
  "bit": 6
}
```

---

## Quiz (Abschluss-Quiz)

Multiple-Choice-Fragen mit einer richtigen Antwort. Erscheint nach den Aufgaben.

```json
{
  "q": "Was gibt type(42) zurück?",
  "opts": ["<class 'int'>", "<class 'float'>", "<class 'str'>", "42"],
  "ok": 0,
  "ex": "42 ist eine ganze Zahl, also int."
}
```

| Feld | Beschreibung |
|---|---|
| `q` | Frage, max. 500 Zeichen |
| `opts` | Genau 4 Antwortoptionen, je max. 200 Zeichen |
| `ok` | Index der richtigen Antwort (0-basiert) |
| `ex` | Erklärung bei richtiger Antwort |

---

## Regeln & Hinweise

- **Sprache:** Alle Texte auf Deutsch
- **IDs:** Jede `id` (lesson key, task id, poll id) muss innerhalb des Kurses eindeutig sein
- **bit-Werte:** Pro Lektion aufsteigend beginnend mit `1` — niemals wiederholen
- **poll id:** Immer angeben, Format `poll-xxxxxx` (6 zufällige alphanumerische Zeichen)
- **Bilder:** Nur öffentliche HTTPS-URLs
- **code-explain lines:** 0-basierte Zeilennummern des `code`-Feldes
- **Stepper:** `sections` mit 2+ Einträgen → automatische Weiter/Zurück-Navigation

---

## Vollständiges Minimal-Beispiel

```json
{
  "id": "kurs-beispiel",
  "title": "Python Schnellkurs",
  "subtitle": "Die wichtigsten Grundlagen",
  "icon": "🐍",
  "color": "#16a34a",
  "boardEnabled": true,
  "lessons": [
    {
      "key": "l-variablen",
      "title": "Variablen",
      "desc": "Wie man Werte speichert.",
      "icon": "📦",
      "tutorEnabled": false,
      "tutorPrompt": "",
      "sections": [
        {
          "title": "Was ist eine Variable?",
          "blocks": [
            { "type": "text", "html": "<p>Eine Variable ist ein <b>benannter Speicherplatz</b> für einen Wert.</p>" },
            {
              "type": "code-explain",
              "label": "Beispiel",
              "code": "alter = 15\nname = 'Anna'\nprint(name, 'ist', alter, 'Jahre alt.')",
              "annotations": [
                { "lines": [0], "label": "1 – Zahl speichern", "text": "Speichert die Zahl 15 in <code>alter</code>." },
                { "lines": [1], "label": "2 – Text speichern", "text": "Speichert den Text Anna in <code>name</code>." },
                { "lines": [2], "label": "3 – Ausgabe", "text": "Gibt beides zusammen aus." }
              ]
            },
            {
              "type": "poll", "id": "poll-var001", "pollType": "mc",
              "question": "Was speichert alter = 15?",
              "opts": ["Den Text '15'", "Die Zahl 15", "Gar nichts", "Den Namen alter"]
            }
          ]
        }
      ],
      "tasks": [
        {
          "type": "code", "id": "t-var001", "bit": 1,
          "title": "Eigene Variable",
          "desc": "Erstelle eine Variable mein_alter mit deinem Alter und gib sie mit print() aus.",
          "starter": "# Dein Code hier\n",
          "expectedHint": "Nutze: mein_alter = ... und print(mein_alter)",
          "expectedOutput": "",
          "assertions": []
        }
      ],
      "quiz": [
        {
          "q": "Wie speichert man den Wert 42 in einer Variable x?",
          "opts": ["x == 42", "x = 42", "var x = 42", "int x = 42"],
          "ok": 1,
          "ex": "In Python weist man Werte mit = zu, ohne Typangabe."
        }
      ]
    }
  ]
}
```
