{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://teach.roughdraftlife.de/kursformat/1/kursformat.schema.json",
  "title": "Kursformat des Inline-Editors, Formatversion 1",
  "description": "Eine Kursdatei beschreibt einen vollständigen Kurs: Lektionen, Blöcke, Fragen, Weiter-Balken, Abschlussregeln und Medienliste. Editor, KI-Framework, Drehbuch-Export und SCORM-Export verwenden genau dieses Format.",
  "type": "object",
  "properties": {
    "$schema": {
      "type": "string"
    },
    "formatVersion": {
      "const": 1,
      "description": "Nummer des Kursformats, mit dem die Datei gespeichert wurde. Ganze Zahl, steigt bei jeder Formatänderung um 1. Ältere Dateien werden über die Migrationskette angehoben; neuere lehnt der Editor ab."
    },
    "id": {
      "type": "string",
      "description": "Kennung des Kurses. Vergibt der Editor; KI-erzeugte Dateien lassen sie weg.",
      "minLength": 1
    },
    "meta": {
      "type": "object",
      "properties": {
        "title": {
          "type": "string",
          "minLength": 1
        },
        "description": {
          "type": "string"
        },
        "language": {
          "type": "string",
          "pattern": "^[a-z]{2}(-[A-Z]{2})?$",
          "default": "de"
        },
        "courseVersion": {
          "type": "string",
          "pattern": "^[0-9]+\\.[0-9]+(\\.[0-9]+)?$",
          "default": "1.0",
          "description": "Version des Kursinhalts (z. B. 1.6). Steht im Drehbuch und im SCORM-Paket; nicht zu verwechseln mit formatVersion."
        },
        "authors": {
          "type": "array",
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "sources": {
          "type": "string",
          "description": "Freitext, z. B. zugrunde liegende Dokumente."
        },
        "durationMinutes": {
          "type": "integer",
          "minimum": 1
        },
        "origin": {
          "type": "object",
          "description": "Woher die Datei stammt.",
          "properties": {
            "kind": {
              "enum": [
                "editor",
                "ai",
                "riseImport"
              ]
            },
            "note": {
              "type": "string"
            }
          },
          "required": [
            "kind"
          ],
          "additionalProperties": false
        }
      },
      "required": [
        "title"
      ],
      "additionalProperties": false
    },
    "settings": {
      "type": "object",
      "properties": {
        "navigation": {
          "enum": [
            "free",
            "linear"
          ],
          "default": "free",
          "description": "linear: jede Lektion erst nach Abschluss der vorherigen."
        },
        "sidebar": {
          "enum": [
            "open",
            "closed",
            "hidden"
          ],
          "default": "open"
        },
        "search": {
          "type": "boolean",
          "default": true,
          "description": "Suche im Kurs."
        },
        "lessonCounter": {
          "type": "boolean",
          "default": true,
          "description": "Lektionszähler."
        },
        "prevNextButtons": {
          "type": "boolean",
          "default": true,
          "description": "Vor/Zurück-Schaltflächen."
        },
        "startScreen": {
          "type": "object",
          "properties": {
            "enabled": {
              "type": "boolean",
              "default": true,
              "description": "Startbildschirm zeigen."
            },
            "media": {
              "$ref": "#/$defs/mediaId"
            }
          },
          "additionalProperties": false
        },
        "theme": {
          "type": "string",
          "default": "standard",
          "description": "Name des Editor-Themes. Das Design selbst steht nie in der Kursdatei."
        }
      },
      "additionalProperties": false
    },
    "sections": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "$ref": "#/$defs/sectionId"
          },
          "title": {
            "type": "string",
            "minLength": 1
          }
        },
        "required": [
          "id",
          "title"
        ],
        "additionalProperties": false
      },
      "description": "Optionale Abschnitte, die Lektionen gruppieren."
    },
    "questionGroups": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "$ref": "#/$defs/groupId"
          },
          "title": {
            "type": "string",
            "minLength": 1
          }
        },
        "required": [
          "id",
          "title"
        ],
        "additionalProperties": false
      },
      "description": "Fragengruppen, z. B. Zielgruppen-Varianten."
    },
    "lessons": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/lesson"
      },
      "minItems": 1
    },
    "completion": {
      "type": "object",
      "properties": {
        "report": {
          "enum": [
            "auto",
            "completed",
            "passed"
          ],
          "default": "auto",
          "description": "Was an Moodle gemeldet wird. auto: passed/failed, sobald eine Bewertungsregel (minScore, mandatoryQuestions, packagePassed) gilt, sonst completed."
        },
        "rules": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/completionRule"
          },
          "minItems": 1,
          "description": "Abschlussregeln. Alle müssen erfüllt sein (UND)."
        }
      },
      "required": [
        "rules"
      ],
      "additionalProperties": false
    },
    "media": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/mediaItem"
      },
      "description": "Medienliste des Kurses."
    },
    "idCounters": {
      "type": "object",
      "description": "Höchste je vergebene Nummer pro Kennungsart. Verhindert, dass die Kennung eines gelöschten Blocks neu vergeben wird. Fehlt sie (KI-Datei), berechnet der Editor sie beim Import.",
      "properties": {
        "L": {
          "type": "integer",
          "minimum": 0
        },
        "B": {
          "type": "integer",
          "minimum": 0
        },
        "F": {
          "type": "integer",
          "minimum": 0
        },
        "M": {
          "type": "integer",
          "minimum": 0
        },
        "A": {
          "type": "integer",
          "minimum": 0
        },
        "G": {
          "type": "integer",
          "minimum": 0
        }
      },
      "additionalProperties": false
    },
    "x": {
      "$ref": "#/$defs/extension"
    }
  },
  "required": [
    "formatVersion",
    "meta",
    "lessons",
    "completion"
  ],
  "additionalProperties": false,
  "$defs": {
    "lessonId": {
      "type": "string",
      "pattern": "^L[1-9][0-9]*$",
      "description": "Feste Kennung einer Lektion, z. B. L3. Wird nie geändert und nie neu vergeben."
    },
    "blockId": {
      "type": "string",
      "pattern": "^[BF][1-9][0-9]*$",
      "description": "Feste Kennung eines Blocks. Fragen beginnen mit F (F6), alle anderen Blöcke mit B (B27). Kursweit eindeutig, unabhängig von Lektion und Position; bleibt beim Verschieben, Ändern und Wiedereinlesen gleich."
    },
    "questionId": {
      "type": "string",
      "pattern": "^F[1-9][0-9]*$",
      "description": "Feste Kennung einer Frage, z. B. F6. Erscheint im Drehbuch und im Moodle-Bericht (cmi.interactions)."
    },
    "mediaId": {
      "type": "string",
      "pattern": "^M[1-9][0-9]*$",
      "description": "Kennung eines Eintrags in der Medienliste, z. B. M4."
    },
    "sectionId": {
      "type": "string",
      "pattern": "^A[1-9][0-9]*$",
      "description": "Kennung eines Abschnitts (Gruppe von Lektionen), z. B. A1."
    },
    "groupId": {
      "type": "string",
      "pattern": "^G[1-9][0-9]*$",
      "description": "Kennung einer Fragengruppe, z. B. G1."
    },
    "elementId": {
      "type": "string",
      "pattern": "^[a-z][a-z0-9]{0,11}$",
      "description": "Feste Kennung eines Elements innerhalb eines Blocks (Akkordeon-Abschnitt, Karte, Marker, Schritt, Antwort …), z. B. e1. Eindeutig innerhalb des Blocks. Vollständige Adresse: Block-Kennung + Element-Kennung, z. B. B12/e3."
    },
    "extension": {
      "type": "object",
      "description": "Freier Bereich für Daten, die diese Formatversion nicht kennt. Wird beim Laden, Speichern und Migrieren unverändert mitgeführt."
    },
    "color": {
      "type": "string",
      "pattern": "^(theme:[a-z][a-z0-9-]*|#[0-9a-fA-F]{6})$",
      "description": "Farbe aus der Theme-Palette (z. B. theme:accent) oder frei als Hex-Wert (#285f9f)."
    },
    "percent": {
      "type": "number",
      "minimum": 0,
      "maximum": 100
    },
    "richText": {
      "description": "Formatierter Text. Entweder ein einfacher String (reiner Text, Leerzeile = neuer Absatz, keine Formatierung) oder ein strukturiertes Dokument. Nie HTML.",
      "anyOf": [
        {
          "type": "string"
        },
        {
          "$ref": "#/$defs/richDoc"
        }
      ]
    },
    "richDoc": {
      "type": "object",
      "description": "Strukturiertes Textdokument (kompatibel zu TipTap/ProseMirror).",
      "properties": {
        "type": {
          "const": "doc"
        },
        "content": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/richBlock"
          }
        }
      },
      "required": [
        "type",
        "content"
      ],
      "additionalProperties": false
    },
    "richBlock": {
      "type": "object",
      "properties": {
        "type": {
          "enum": [
            "paragraph",
            "heading",
            "bulletList",
            "orderedList",
            "listItem",
            "blockquote",
            "callout",
            "table",
            "tableRow",
            "tableCell",
            "tableHeader"
          ]
        }
      },
      "required": [
        "type"
      ],
      "allOf": [
        {
          "if": {
            "properties": {
              "type": {
                "const": "paragraph"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "properties": {
              "attrs": {
                "type": "object",
                "properties": {
                  "style": {
                    "enum": [
                      "normal",
                      "small"
                    ],
                    "default": "normal"
                  },
                  "align": {
                    "enum": [
                      "left",
                      "center",
                      "right",
                      "justify"
                    ],
                    "default": "left"
                  },
                  "indent": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 4,
                    "default": 0
                  },
                  "spacing": {
                    "enum": [
                      "tight",
                      "normal",
                      "wide"
                    ],
                    "default": "normal"
                  }
                },
                "additionalProperties": false
              },
              "content": {
                "type": "array",
                "items": {
                  "$ref": "#/$defs/richInline"
                }
              }
            }
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "heading"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "properties": {
              "attrs": {
                "type": "object",
                "properties": {
                  "level": {
                    "enum": [
                      1,
                      2,
                      3
                    ]
                  },
                  "align": {
                    "enum": [
                      "left",
                      "center",
                      "right",
                      "justify"
                    ],
                    "default": "left"
                  }
                },
                "required": [
                  "level"
                ],
                "additionalProperties": false
              },
              "content": {
                "type": "array",
                "items": {
                  "$ref": "#/$defs/richInline"
                }
              }
            },
            "required": [
              "attrs",
              "content"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "bulletList"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "properties": {
              "content": {
                "type": "array",
                "items": {
                  "$ref": "#/$defs/richBlock"
                },
                "minItems": 1
              }
            },
            "required": [
              "content"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "orderedList"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "properties": {
              "attrs": {
                "type": "object",
                "properties": {
                  "start": {
                    "type": "integer",
                    "minimum": 0,
                    "default": 1
                  }
                },
                "additionalProperties": false
              },
              "content": {
                "type": "array",
                "items": {
                  "$ref": "#/$defs/richBlock"
                },
                "minItems": 1
              }
            },
            "required": [
              "content"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "listItem"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "properties": {
              "content": {
                "type": "array",
                "items": {
                  "$ref": "#/$defs/richBlock"
                },
                "minItems": 1
              }
            },
            "required": [
              "content"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "blockquote"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Zitat",
            "properties": {
              "content": {
                "type": "array",
                "items": {
                  "$ref": "#/$defs/richBlock"
                },
                "minItems": 1
              }
            },
            "required": [
              "content"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "callout"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Hinweis",
            "properties": {
              "content": {
                "type": "array",
                "items": {
                  "$ref": "#/$defs/richBlock"
                },
                "minItems": 1
              }
            },
            "required": [
              "content"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "table"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "properties": {
              "content": {
                "type": "array",
                "items": {
                  "$ref": "#/$defs/richBlock"
                },
                "minItems": 1
              }
            },
            "required": [
              "content"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "tableRow"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "properties": {
              "content": {
                "type": "array",
                "items": {
                  "$ref": "#/$defs/richBlock"
                },
                "minItems": 1
              }
            },
            "required": [
              "content"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "tableCell"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "properties": {
              "attrs": {
                "type": "object",
                "properties": {
                  "colspan": {
                    "type": "integer",
                    "minimum": 1,
                    "default": 1
                  },
                  "rowspan": {
                    "type": "integer",
                    "minimum": 1,
                    "default": 1
                  },
                  "widthPercent": {
                    "$ref": "#/$defs/percent"
                  },
                  "background": {
                    "$ref": "#/$defs/color"
                  },
                  "align": {
                    "enum": [
                      "left",
                      "center",
                      "right",
                      "justify"
                    ],
                    "default": "left"
                  }
                },
                "additionalProperties": false
              },
              "content": {
                "type": "array",
                "items": {
                  "$ref": "#/$defs/richBlock"
                },
                "minItems": 1
              }
            },
            "required": [
              "content"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "tableHeader"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "properties": {
              "attrs": {
                "type": "object",
                "properties": {
                  "colspan": {
                    "type": "integer",
                    "minimum": 1,
                    "default": 1
                  },
                  "rowspan": {
                    "type": "integer",
                    "minimum": 1,
                    "default": 1
                  },
                  "widthPercent": {
                    "$ref": "#/$defs/percent"
                  },
                  "background": {
                    "$ref": "#/$defs/color"
                  },
                  "align": {
                    "enum": [
                      "left",
                      "center",
                      "right",
                      "justify"
                    ],
                    "default": "left"
                  }
                },
                "additionalProperties": false
              },
              "content": {
                "type": "array",
                "items": {
                  "$ref": "#/$defs/richBlock"
                },
                "minItems": 1
              }
            },
            "required": [
              "content"
            ]
          }
        }
      ],
      "unevaluatedProperties": false
    },
    "richMark": {
      "type": "object",
      "properties": {
        "type": {
          "enum": [
            "bold",
            "italic",
            "underline",
            "strike",
            "superscript",
            "subscript",
            "code",
            "link",
            "textColor",
            "highlight",
            "fontSize"
          ]
        }
      },
      "required": [
        "type"
      ],
      "allOf": [
        {
          "if": {
            "properties": {
              "type": {
                "const": "link"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "properties": {
              "attrs": {
                "type": "object",
                "properties": {
                  "kind": {
                    "enum": [
                      "external",
                      "lesson",
                      "block",
                      "email",
                      "download"
                    ]
                  }
                },
                "required": [
                  "kind"
                ],
                "allOf": [
                  {
                    "if": {
                      "properties": {
                        "kind": {
                          "const": "external"
                        }
                      },
                      "required": [
                        "kind"
                      ]
                    },
                    "then": {
                      "type": "object",
                      "properties": {
                        "href": {
                          "type": "string",
                          "pattern": "^https?://"
                        }
                      },
                      "required": [
                        "href"
                      ]
                    }
                  },
                  {
                    "if": {
                      "properties": {
                        "kind": {
                          "const": "lesson"
                        }
                      },
                      "required": [
                        "kind"
                      ]
                    },
                    "then": {
                      "type": "object",
                      "properties": {
                        "lessonId": {
                          "$ref": "#/$defs/lessonId"
                        }
                      },
                      "required": [
                        "lessonId"
                      ]
                    }
                  },
                  {
                    "if": {
                      "properties": {
                        "kind": {
                          "const": "block"
                        }
                      },
                      "required": [
                        "kind"
                      ]
                    },
                    "then": {
                      "type": "object",
                      "properties": {
                        "blockId": {
                          "$ref": "#/$defs/blockId"
                        }
                      },
                      "required": [
                        "blockId"
                      ]
                    }
                  },
                  {
                    "if": {
                      "properties": {
                        "kind": {
                          "const": "email"
                        }
                      },
                      "required": [
                        "kind"
                      ]
                    },
                    "then": {
                      "type": "object",
                      "properties": {
                        "address": {
                          "type": "string",
                          "pattern": "^[^@\\s]+@[^@\\s]+$"
                        }
                      },
                      "required": [
                        "address"
                      ]
                    }
                  },
                  {
                    "if": {
                      "properties": {
                        "kind": {
                          "const": "download"
                        }
                      },
                      "required": [
                        "kind"
                      ]
                    },
                    "then": {
                      "type": "object",
                      "properties": {
                        "mediaId": {
                          "$ref": "#/$defs/mediaId"
                        }
                      },
                      "required": [
                        "mediaId"
                      ]
                    }
                  }
                ],
                "unevaluatedProperties": false
              }
            },
            "required": [
              "attrs"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "textColor"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "properties": {
              "attrs": {
                "type": "object",
                "properties": {
                  "color": {
                    "$ref": "#/$defs/color"
                  }
                },
                "required": [
                  "color"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "attrs"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "highlight"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "properties": {
              "attrs": {
                "type": "object",
                "properties": {
                  "color": {
                    "$ref": "#/$defs/color"
                  }
                },
                "required": [
                  "color"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "attrs"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "fontSize"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Schriftgröße in festen Stufen statt Pixelwerten.",
            "properties": {
              "attrs": {
                "type": "object",
                "properties": {
                  "size": {
                    "enum": [
                      "small",
                      "normal",
                      "large",
                      "xlarge"
                    ]
                  }
                },
                "required": [
                  "size"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "attrs"
            ]
          }
        }
      ],
      "unevaluatedProperties": false
    },
    "richInline": {
      "oneOf": [
        {
          "type": "object",
          "properties": {
            "type": {
              "const": "text"
            },
            "text": {
              "type": "string",
              "minLength": 1
            },
            "marks": {
              "type": "array",
              "items": {
                "$ref": "#/$defs/richMark"
              }
            }
          },
          "required": [
            "type",
            "text"
          ],
          "additionalProperties": false
        },
        {
          "type": "object",
          "description": "Zeilenumbruch ohne neuen Absatz.",
          "properties": {
            "type": {
              "const": "hardBreak"
            }
          },
          "required": [
            "type"
          ],
          "additionalProperties": false
        }
      ]
    },
    "mediaItem": {
      "type": "object",
      "description": "Eintrag der Medienliste. Blöcke verweisen nur per Kennung darauf. status=placeholder ist der Medien-Platzhalter für KI-erzeugte Kurse: Die KI beschreibt, was gebraucht wird; der Editor zeigt daraus die Aufgabenliste »Medien ergänzen«.",
      "properties": {
        "id": {
          "$ref": "#/$defs/mediaId"
        },
        "kind": {
          "enum": [
            "image",
            "video",
            "audio",
            "document",
            "package"
          ],
          "description": "package = HTML-Paket (Storyline-/H5P-Ausgabe als ZIP)."
        },
        "status": {
          "enum": [
            "ready",
            "placeholder"
          ]
        },
        "file": {
          "type": "string",
          "pattern": "^media/[^\\\\]+$",
          "description": "Pfad relativ zur Kursdatei, z. B. media/M4-werkzeug.jpg."
        },
        "placeholder": {
          "type": "object",
          "properties": {
            "description": {
              "type": "string",
              "minLength": 1,
              "description": "Was zu sehen/hören sein soll, z. B. »Foto: Sammelplatzschild vor dem Haupteingang«."
            },
            "hint": {
              "type": "string",
              "description": "Optionaler Produktionshinweis, z. B. Format, Perspektive, Länge."
            }
          },
          "required": [
            "description"
          ],
          "additionalProperties": false
        },
        "title": {
          "type": "string"
        },
        "alt": {
          "type": "string",
          "description": "Alternativtext (Bilder). Erscheint auch im Drehbuch. Leer nur bei decorative=true."
        },
        "decorative": {
          "type": "boolean",
          "default": false,
          "description": "Reines Schmuckbild ohne Informationsgehalt."
        },
        "credit": {
          "type": "string",
          "description": "Quelle/Urheber*in/Lizenz."
        },
        "mime": {
          "type": "string"
        },
        "bytes": {
          "type": "integer",
          "minimum": 0
        },
        "width": {
          "type": "integer",
          "minimum": 1
        },
        "height": {
          "type": "integer",
          "minimum": 1
        },
        "durationSeconds": {
          "type": "number",
          "minimum": 0
        },
        "poster": {
          "$ref": "#/$defs/mediaId",
          "description": "Vorschaubild eines Videos."
        },
        "captions": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "language": {
                "type": "string",
                "pattern": "^[a-z]{2}(-[A-Z]{2})?$"
              },
              "file": {
                "type": "string",
                "pattern": "^media/[^\\\\]+\\.vtt$"
              }
            },
            "required": [
              "language",
              "file"
            ],
            "additionalProperties": false
          },
          "description": "Untertitel (WebVTT)."
        },
        "transcript": {
          "type": "string",
          "description": "Vollständiges Transkript (Video/Audio). Erscheint im Drehbuch."
        },
        "slides": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "time": {
                "type": "number",
                "minimum": 0,
                "description": "Sekunden ab Videostart."
              },
              "text": {
                "type": "string"
              },
              "notes": {
                "type": "string"
              },
              "image": {
                "$ref": "#/$defs/mediaId"
              }
            },
            "required": [
              "time"
            ],
            "additionalProperties": false
          },
          "description": "Folienmarker eines Folienvideos: Zeitpunkt, Folientext, Sprechernotizen."
        },
        "content": {
          "type": "object",
          "description": "Inhaltserfassung: der Sinngehalt des Mediums als Text. Für Lernende unsichtbar; dient Volltextsuche, Drehbuch und QM-Abgleich. Wird beim Hochladen automatisch erzeugt und kann korrigiert werden.",
          "properties": {
            "text": {
              "type": "string",
              "description": "Was das Medium inhaltlich aussagt: z. B. Text und Tabellen in einer Grafik, Kernaussage eines Diagramms. Bei Videos steht der Folieninhalt in slides, das Gesprochene in transcript."
            },
            "source": {
              "enum": [
                "ai",
                "author",
                "pptx",
                "ocr"
              ],
              "description": "Woher die Erfassung stammt."
            },
            "reviewed": {
              "type": "boolean",
              "default": false,
              "description": "Von einem Menschen geprüft."
            },
            "updated": {
              "type": "string",
              "format": "date"
            }
          },
          "required": [
            "text",
            "source"
          ],
          "additionalProperties": false
        },
        "entry": {
          "type": "string",
          "description": "Startdatei innerhalb eines HTML-Pakets, z. B. story.html."
        },
        "x": {
          "$ref": "#/$defs/extension"
        }
      },
      "required": [
        "id",
        "kind",
        "status"
      ],
      "allOf": [
        {
          "if": {
            "properties": {
              "status": {
                "const": "ready"
              }
            },
            "required": [
              "status"
            ]
          },
          "then": {
            "required": [
              "file"
            ]
          },
          "else": {
            "required": [
              "placeholder"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "kind": {
                "const": "image"
              }
            },
            "required": [
              "kind"
            ]
          },
          "then": {
            "required": [
              "alt"
            ]
          }
        }
      ],
      "additionalProperties": false
    },
    "block": {
      "type": "object",
      "properties": {
        "id": {
          "$ref": "#/$defs/blockId"
        },
        "type": {
          "enum": [
            "text",
            "image",
            "statement",
            "quote",
            "list",
            "accordion",
            "flashcards",
            "video",
            "audio",
            "gallery",
            "divider",
            "continue",
            "button",
            "hotspots",
            "process",
            "question",
            "htmlPackage"
          ]
        },
        "layout": {
          "type": "object",
          "description": "Darstellung des Blocks. Farben und Schriften kommen immer aus dem Editor-Theme.",
          "properties": {
            "spaceTop": {
              "enum": [
                "none",
                "small",
                "medium",
                "large"
              ],
              "default": "medium"
            },
            "spaceBottom": {
              "enum": [
                "none",
                "small",
                "medium",
                "large"
              ],
              "default": "medium"
            },
            "background": {
              "type": "object",
              "properties": {
                "kind": {
                  "enum": [
                    "light",
                    "dark",
                    "color",
                    "image"
                  ]
                },
                "color": {
                  "$ref": "#/$defs/color"
                },
                "media": {
                  "$ref": "#/$defs/mediaId"
                }
              },
              "required": [
                "kind"
              ],
              "additionalProperties": false,
              "allOf": [
                {
                  "if": {
                    "properties": {
                      "kind": {
                        "const": "color"
                      }
                    }
                  },
                  "then": {
                    "required": [
                      "color"
                    ]
                  }
                },
                {
                  "if": {
                    "properties": {
                      "kind": {
                        "const": "image"
                      }
                    }
                  },
                  "then": {
                    "required": [
                      "media"
                    ]
                  }
                }
              ]
            },
            "animate": {
              "type": "boolean",
              "default": true,
              "description": "Einblendanimation."
            }
          },
          "additionalProperties": false
        },
        "optional": {
          "type": "boolean",
          "default": false,
          "description": "Block zählt nicht für Weiter-Balken-Sperren und Fortschritt."
        },
        "x": {
          "$ref": "#/$defs/extension"
        }
      },
      "required": [
        "id",
        "type"
      ],
      "allOf": [
        {
          "if": {
            "properties": {
              "type": {
                "const": "question"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "properties": {
              "id": {
                "$ref": "#/$defs/questionId"
              }
            }
          },
          "else": {
            "properties": {
              "id": {
                "type": "string",
                "pattern": "^B[1-9][0-9]*$"
              }
            }
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "text"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Text: Absätze, Überschriften, Listen, Tabellen.",
            "properties": {
              "content": {
                "$ref": "#/$defs/richText"
              }
            },
            "required": [
              "content"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "image"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "properties": {
              "variant": {
                "enum": [
                  "standard",
                  "hero",
                  "withText"
                ],
                "default": "standard",
                "description": "hero = volle Breite; withText = Bild neben Text."
              },
              "media": {
                "$ref": "#/$defs/mediaId"
              },
              "caption": {
                "$ref": "#/$defs/richText"
              },
              "text": {
                "$ref": "#/$defs/richText",
                "description": "Text neben dem Bild (withText)."
              },
              "imagePosition": {
                "enum": [
                  "left",
                  "right"
                ],
                "default": "left"
              }
            },
            "required": [
              "media"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "statement"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Hervorhebung.",
            "properties": {
              "variant": {
                "enum": [
                  "a",
                  "b",
                  "c",
                  "d",
                  "note"
                ],
                "default": "a",
                "description": "Vier Hervorhebungsstile (a–d) und die Hinweis-Box (note)."
              },
              "content": {
                "$ref": "#/$defs/richText"
              }
            },
            "required": [
              "content"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "quote"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Zitat oder Sprechblase.",
            "properties": {
              "variant": {
                "enum": [
                  "quote",
                  "bubble"
                ],
                "default": "quote"
              },
              "content": {
                "$ref": "#/$defs/richText"
              },
              "author": {
                "type": "string"
              },
              "role": {
                "type": "string"
              },
              "avatar": {
                "$ref": "#/$defs/mediaId"
              },
              "backgroundMedia": {
                "$ref": "#/$defs/mediaId"
              }
            },
            "required": [
              "content"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "list"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Liste. checklist = Checkboxen zum Abhaken; abgeschlossen, wenn alle abgehakt sind.",
            "properties": {
              "variant": {
                "enum": [
                  "bullet",
                  "numbered",
                  "checklist"
                ],
                "default": "bullet"
              },
              "items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "$ref": "#/$defs/elementId"
                    },
                    "content": {
                      "$ref": "#/$defs/richText"
                    }
                  },
                  "required": [
                    "id",
                    "content"
                  ],
                  "additionalProperties": false
                },
                "minItems": 1
              }
            },
            "required": [
              "items"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "accordion"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Akkordeon oder Tabs. Abgeschlossen, wenn alle Abschnitte geöffnet wurden.",
            "properties": {
              "variant": {
                "enum": [
                  "accordion",
                  "tabs"
                ],
                "default": "accordion"
              },
              "items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "$ref": "#/$defs/elementId"
                    },
                    "title": {
                      "type": "string",
                      "minLength": 1
                    },
                    "content": {
                      "$ref": "#/$defs/richText"
                    },
                    "media": {
                      "$ref": "#/$defs/mediaId"
                    }
                  },
                  "required": [
                    "id",
                    "title",
                    "content"
                  ],
                  "additionalProperties": false
                },
                "minItems": 1
              }
            },
            "required": [
              "items"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "flashcards"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Lernkarten. Abgeschlossen, wenn alle umgedreht wurden.",
            "properties": {
              "variant": {
                "enum": [
                  "grid",
                  "stack"
                ],
                "default": "grid"
              },
              "cards": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "$ref": "#/$defs/elementId"
                    },
                    "front": {
                      "type": "object",
                      "properties": {
                        "text": {
                          "$ref": "#/$defs/richText"
                        },
                        "media": {
                          "$ref": "#/$defs/mediaId"
                        }
                      },
                      "additionalProperties": false
                    },
                    "back": {
                      "type": "object",
                      "properties": {
                        "text": {
                          "$ref": "#/$defs/richText"
                        },
                        "media": {
                          "$ref": "#/$defs/mediaId"
                        }
                      },
                      "additionalProperties": false
                    }
                  },
                  "required": [
                    "id",
                    "front",
                    "back"
                  ],
                  "additionalProperties": false
                },
                "minItems": 1
              }
            },
            "required": [
              "cards"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "video"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Video. Poster, Untertitel, Transkript und Folienmarker stehen am Medieneintrag.",
            "properties": {
              "source": {
                "oneOf": [
                  {
                    "type": "object",
                    "properties": {
                      "kind": {
                        "const": "file"
                      },
                      "media": {
                        "$ref": "#/$defs/mediaId"
                      }
                    },
                    "required": [
                      "kind",
                      "media"
                    ],
                    "additionalProperties": false
                  },
                  {
                    "type": "object",
                    "properties": {
                      "kind": {
                        "const": "embed"
                      },
                      "provider": {
                        "enum": [
                          "youtube",
                          "vimeo"
                        ]
                      },
                      "url": {
                        "type": "string",
                        "pattern": "^https://"
                      },
                      "transcript": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "kind",
                      "provider",
                      "url"
                    ],
                    "additionalProperties": false
                  }
                ]
              },
              "caption": {
                "$ref": "#/$defs/richText"
              },
              "completeAtPercent": {
                "$ref": "#/$defs/percent",
                "default": 90,
                "description": "Anteil, ab dem das Video als gesehen gilt."
              }
            },
            "required": [
              "source"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "audio"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Audio. Transkript steht am Medieneintrag.",
            "properties": {
              "media": {
                "$ref": "#/$defs/mediaId"
              },
              "caption": {
                "$ref": "#/$defs/richText"
              },
              "completeAtPercent": {
                "$ref": "#/$defs/percent",
                "default": 90
              }
            },
            "required": [
              "media"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "gallery"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Bildergalerie.",
            "properties": {
              "variant": {
                "enum": [
                  "columns2",
                  "columns3",
                  "columns4",
                  "carousel"
                ],
                "default": "columns3"
              },
              "items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "$ref": "#/$defs/elementId"
                    },
                    "media": {
                      "$ref": "#/$defs/mediaId"
                    },
                    "caption": {
                      "$ref": "#/$defs/richText"
                    }
                  },
                  "required": [
                    "id",
                    "media"
                  ],
                  "additionalProperties": false
                },
                "minItems": 2
              }
            },
            "required": [
              "items"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "divider"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Trenner ohne Sperre.",
            "properties": {
              "variant": {
                "enum": [
                  "line",
                  "numbered"
                ],
                "default": "line"
              },
              "label": {
                "type": "string",
                "description": "Optionale Beschriftung; bei numbered wird sonst automatisch gezählt."
              }
            }
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "continue"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Weiter-Balken. Alles darunter bleibt verborgen, bis geklickt wurde bzw. die Sperre erfüllt ist.",
            "properties": {
              "label": {
                "type": "string",
                "default": "Weiter"
              },
              "lock": {
                "enum": [
                  "none",
                  "previousBlock",
                  "allSinceLastContinue"
                ],
                "default": "none",
                "description": "Sperre des Weiter-Balkens. none: frei, nur klicken. previousBlock: erst den Block direkt darüber abschließen. allSinceLastContinue: alle Blöcke seit dem letzten Weiter-Balken (bzw. Lektionsanfang) abschließen."
              },
              "lockedHint": {
                "type": "string",
                "default": "Bitte schließen Sie zuerst die Inhalte oberhalb ab.",
                "description": "Hinweistext am gesperrten Balken."
              },
              "color": {
                "$ref": "#/$defs/color"
              }
            },
            "required": [
              "lock"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "button"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "properties": {
              "label": {
                "type": "string",
                "minLength": 1
              },
              "description": {
                "$ref": "#/$defs/richText"
              },
              "action": {
                "type": "object",
                "properties": {
                  "kind": {
                    "enum": [
                      "link",
                      "lesson",
                      "block",
                      "exit"
                    ]
                  }
                },
                "required": [
                  "kind"
                ],
                "allOf": [
                  {
                    "if": {
                      "properties": {
                        "kind": {
                          "const": "link"
                        }
                      },
                      "required": [
                        "kind"
                      ]
                    },
                    "then": {
                      "type": "object",
                      "properties": {
                        "url": {
                          "type": "string",
                          "pattern": "^https?://"
                        },
                        "newWindow": {
                          "type": "boolean",
                          "default": true,
                          "description": "In neuem Fenster öffnen."
                        }
                      },
                      "required": [
                        "url"
                      ]
                    }
                  },
                  {
                    "if": {
                      "properties": {
                        "kind": {
                          "const": "lesson"
                        }
                      },
                      "required": [
                        "kind"
                      ]
                    },
                    "then": {
                      "type": "object",
                      "properties": {
                        "lessonId": {
                          "$ref": "#/$defs/lessonId"
                        }
                      },
                      "required": [
                        "lessonId"
                      ]
                    }
                  },
                  {
                    "if": {
                      "properties": {
                        "kind": {
                          "const": "block"
                        }
                      },
                      "required": [
                        "kind"
                      ]
                    },
                    "then": {
                      "type": "object",
                      "properties": {
                        "blockId": {
                          "$ref": "#/$defs/blockId"
                        }
                      },
                      "required": [
                        "blockId"
                      ]
                    }
                  },
                  {
                    "if": {
                      "properties": {
                        "kind": {
                          "const": "exit"
                        }
                      },
                      "required": [
                        "kind"
                      ]
                    },
                    "then": {
                      "type": "object",
                      "description": "Kurs beenden und zurück zu Moodle.",
                      "properties": {}
                    }
                  }
                ],
                "unevaluatedProperties": false
              }
            },
            "required": [
              "label",
              "action"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "hotspots"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Bild mit Markern. Abgeschlossen, wenn alle Marker angeklickt wurden.",
            "properties": {
              "media": {
                "$ref": "#/$defs/mediaId"
              },
              "markers": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "$ref": "#/$defs/elementId"
                    },
                    "x": {
                      "$ref": "#/$defs/percent",
                      "description": "Position von links in Prozent."
                    },
                    "y": {
                      "$ref": "#/$defs/percent",
                      "description": "Position von oben in Prozent."
                    },
                    "title": {
                      "type": "string",
                      "minLength": 1
                    },
                    "content": {
                      "$ref": "#/$defs/richText"
                    },
                    "media": {
                      "$ref": "#/$defs/mediaId"
                    }
                  },
                  "required": [
                    "id",
                    "x",
                    "y",
                    "title"
                  ],
                  "additionalProperties": false
                },
                "minItems": 1
              }
            },
            "required": [
              "media",
              "markers"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "process"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Prozess Schritt für Schritt. Abgeschlossen, wenn alle Schritte durchlaufen wurden.",
            "properties": {
              "intro": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "content": {
                    "$ref": "#/$defs/richText"
                  }
                },
                "additionalProperties": false
              },
              "steps": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "$ref": "#/$defs/elementId"
                    },
                    "title": {
                      "type": "string",
                      "minLength": 1
                    },
                    "content": {
                      "$ref": "#/$defs/richText"
                    },
                    "media": {
                      "$ref": "#/$defs/mediaId"
                    }
                  },
                  "required": [
                    "id",
                    "title",
                    "content"
                  ],
                  "additionalProperties": false
                },
                "minItems": 1
              },
              "summary": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "content": {
                    "$ref": "#/$defs/richText"
                  }
                },
                "additionalProperties": false
              }
            },
            "required": [
              "steps"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "question"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Frage. Als Wissensabfrage zwischen Inhaltsblöcken oder als Teil einer Quiz-Lektion. Die Sortierübung ist die Frageart sorting.",
            "properties": {
              "kind": {
                "enum": [
                  "singleChoice",
                  "multipleChoice",
                  "matching",
                  "sorting"
                ]
              },
              "prompt": {
                "$ref": "#/$defs/richText",
                "description": "Fragetext."
              },
              "media": {
                "$ref": "#/$defs/mediaId",
                "description": "Bild oder Video zur Frage."
              },
              "points": {
                "type": "number",
                "minimum": 0,
                "default": 1,
                "description": "Punkte bei richtiger Antwort."
              },
              "scored": {
                "type": "boolean",
                "default": false,
                "description": "Zählt zur Bewertung. In Quiz-Lektionen zählen alle Fragen; dort wird der Wert ignoriert."
              },
              "mandatory": {
                "type": "boolean",
                "default": false,
                "description": "Pflichtfrage: muss richtig beantwortet sein, damit der Kurs bestanden ist – unabhängig von der Gesamtpunktzahl. Wirkt über die Abschlussregel mandatoryQuestions."
              },
              "scoring": {
                "enum": [
                  "allOrNothing",
                  "partial"
                ],
                "default": "allOrNothing",
                "description": "partial: anteilige Punkte bei Multiple Choice, Zuordnung und Sortieren."
              },
              "group": {
                "$ref": "#/$defs/groupId",
                "description": "Fragengruppe, z. B. für Zielgruppen-Varianten."
              },
              "shuffle": {
                "type": "boolean",
                "default": true,
                "description": "Antworten/Karten mischen."
              },
              "completeWhen": {
                "enum": [
                  "answered",
                  "correct"
                ],
                "default": "answered",
                "description": "Wann der Block für Weiter-Balken als abgeschlossen gilt. correct = Quiz als Sperre."
              },
              "allowRetry": {
                "type": "boolean",
                "default": true,
                "description": "Wissensabfrage im Text darf wiederholt werden. In Quiz-Lektionen gelten die Quiz-Einstellungen."
              },
              "feedback": {
                "type": "object",
                "properties": {
                  "mode": {
                    "enum": [
                      "none",
                      "general",
                      "perAnswer"
                    ],
                    "default": "general"
                  },
                  "correct": {
                    "$ref": "#/$defs/richText"
                  },
                  "incorrect": {
                    "$ref": "#/$defs/richText"
                  }
                },
                "required": [
                  "mode"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "kind",
              "prompt"
            ],
            "allOf": [
              {
                "if": {
                  "properties": {
                    "kind": {
                      "const": "singleChoice"
                    }
                  },
                  "required": [
                    "kind"
                  ]
                },
                "then": {
                  "type": "object",
                  "properties": {
                    "answers": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "$ref": "#/$defs/elementId"
                          },
                          "text": {
                            "$ref": "#/$defs/richText"
                          },
                          "media": {
                            "$ref": "#/$defs/mediaId"
                          },
                          "correct": {
                            "type": "boolean"
                          },
                          "feedback": {
                            "$ref": "#/$defs/richText",
                            "description": "Rückmeldung zu dieser Antwort (bei feedback.mode=perAnswer)."
                          }
                        },
                        "required": [
                          "id",
                          "text",
                          "correct"
                        ],
                        "additionalProperties": false
                      },
                      "minItems": 2,
                      "contains": {
                        "properties": {
                          "correct": {
                            "const": true
                          }
                        },
                        "required": [
                          "correct"
                        ]
                      },
                      "minContains": 1,
                      "maxContains": 1,
                      "description": "Genau eine Antwort ist richtig."
                    }
                  },
                  "required": [
                    "answers"
                  ]
                }
              },
              {
                "if": {
                  "properties": {
                    "kind": {
                      "const": "multipleChoice"
                    }
                  },
                  "required": [
                    "kind"
                  ]
                },
                "then": {
                  "type": "object",
                  "properties": {
                    "answers": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "$ref": "#/$defs/elementId"
                          },
                          "text": {
                            "$ref": "#/$defs/richText"
                          },
                          "media": {
                            "$ref": "#/$defs/mediaId"
                          },
                          "correct": {
                            "type": "boolean"
                          },
                          "feedback": {
                            "$ref": "#/$defs/richText",
                            "description": "Rückmeldung zu dieser Antwort (bei feedback.mode=perAnswer)."
                          }
                        },
                        "required": [
                          "id",
                          "text",
                          "correct"
                        ],
                        "additionalProperties": false
                      },
                      "minItems": 2,
                      "contains": {
                        "properties": {
                          "correct": {
                            "const": true
                          }
                        },
                        "required": [
                          "correct"
                        ]
                      },
                      "minContains": 1,
                      "description": "Mindestens eine Antwort ist richtig."
                    }
                  },
                  "required": [
                    "answers"
                  ]
                }
              },
              {
                "if": {
                  "properties": {
                    "kind": {
                      "const": "matching"
                    }
                  },
                  "required": [
                    "kind"
                  ]
                },
                "then": {
                  "type": "object",
                  "properties": {
                    "pairs": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "$ref": "#/$defs/elementId"
                          },
                          "left": {
                            "type": "object",
                            "properties": {
                              "text": {
                                "$ref": "#/$defs/richText"
                              },
                              "media": {
                                "$ref": "#/$defs/mediaId"
                              }
                            },
                            "additionalProperties": false
                          },
                          "right": {
                            "type": "object",
                            "properties": {
                              "text": {
                                "$ref": "#/$defs/richText"
                              },
                              "media": {
                                "$ref": "#/$defs/mediaId"
                              }
                            },
                            "additionalProperties": false
                          }
                        },
                        "required": [
                          "id",
                          "left",
                          "right"
                        ],
                        "additionalProperties": false
                      },
                      "minItems": 2,
                      "description": "Zuordnung: Jede linke Seite gehört zu ihrer rechten Seite."
                    }
                  },
                  "required": [
                    "pairs"
                  ]
                }
              },
              {
                "if": {
                  "properties": {
                    "kind": {
                      "const": "sorting"
                    }
                  },
                  "required": [
                    "kind"
                  ]
                },
                "then": {
                  "type": "object",
                  "properties": {
                    "stacks": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "$ref": "#/$defs/elementId"
                          },
                          "title": {
                            "type": "string",
                            "minLength": 1
                          }
                        },
                        "required": [
                          "id",
                          "title"
                        ],
                        "additionalProperties": false
                      },
                      "minItems": 2,
                      "description": "Stapel, auf die sortiert wird."
                    },
                    "cards": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "$ref": "#/$defs/elementId"
                          },
                          "text": {
                            "type": "string",
                            "minLength": 1
                          },
                          "media": {
                            "$ref": "#/$defs/mediaId"
                          },
                          "stack": {
                            "$ref": "#/$defs/elementId",
                            "description": "Kennung des richtigen Stapels."
                          }
                        },
                        "required": [
                          "id",
                          "text",
                          "stack"
                        ],
                        "additionalProperties": false
                      },
                      "minItems": 2
                    }
                  },
                  "required": [
                    "stacks",
                    "cards"
                  ]
                }
              }
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "htmlPackage"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Eingebettetes HTML-Paket (vorhandene Storyline-/H5P-Ausgabe).",
            "properties": {
              "media": {
                "$ref": "#/$defs/mediaId"
              },
              "title": {
                "type": "string",
                "minLength": 1
              },
              "aspectRatio": {
                "type": "string",
                "pattern": "^[1-9][0-9]*:[1-9][0-9]*$",
                "default": "16:9"
              },
              "completeWhen": {
                "enum": [
                  "opened",
                  "reportsCompleted",
                  "reportsPassed"
                ],
                "default": "opened",
                "description": "Wann der Block als abgeschlossen gilt."
              },
              "reportsScore": {
                "type": "boolean",
                "default": false,
                "description": "Das Paket meldet eine Punktzahl, die in die Kursbewertung eingeht."
              },
              "contentNote": {
                "type": "string",
                "description": "Inhalt in Worten fürs Drehbuch (Folientexte, soweit bekannt)."
              }
            },
            "required": [
              "media",
              "title"
            ]
          }
        }
      ],
      "unevaluatedProperties": false
    },
    "lesson": {
      "type": "object",
      "properties": {
        "id": {
          "$ref": "#/$defs/lessonId"
        },
        "type": {
          "enum": [
            "content",
            "quiz"
          ],
          "default": "content"
        },
        "title": {
          "type": "string",
          "minLength": 1
        },
        "description": {
          "type": "string"
        },
        "section": {
          "$ref": "#/$defs/sectionId",
          "description": "Abschnitt, zu dem die Lektion gehört. Lektionen eines Abschnitts stehen direkt hintereinander."
        },
        "requiresPrevious": {
          "type": "boolean",
          "default": false,
          "description": "Lektion öffnet sich erst, wenn die vorherige abgeschlossen ist. Bei settings.navigation=linear gilt das für alle."
        },
        "blocks": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/block"
          }
        },
        "quiz": {
          "type": "object",
          "description": "Einstellungen einer Quiz-Lektion.",
          "properties": {
            "intro": {
              "$ref": "#/$defs/richText",
              "description": "Text der Startseite."
            },
            "passPercent": {
              "$ref": "#/$defs/percent",
              "description": "Bestehensgrenze dieses Quiz. Ohne Angabe wird nur die Punktzahl gezeigt."
            },
            "requirePass": {
              "type": "boolean",
              "default": false,
              "description": "Lektion gilt erst als abgeschlossen, wenn das Quiz bestanden ist (Quiz als Sperre für die nächste Lektion)."
            },
            "shuffleQuestions": {
              "type": "boolean",
              "default": false,
              "description": "Fragen mischen."
            },
            "drawCount": {
              "type": "integer",
              "minimum": 1,
              "description": "Zufällig so viele Fragen aus dem Pool ziehen (z. B. 10 aus 20). Ohne Angabe: alle."
            },
            "attempts": {
              "type": "object",
              "properties": {
                "max": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "minimum": 1,
                  "default": null,
                  "description": "null = unbegrenzt."
                },
                "retryOnlyWrong": {
                  "type": "boolean",
                  "default": false,
                  "description": "Bei Wiederholung nur falsch beantwortete Fragen erneut stellen."
                }
              },
              "additionalProperties": false
            },
            "showSolutions": {
              "enum": [
                "all",
                "wrong",
                "none"
              ],
              "default": "all"
            },
            "timeLimitMinutes": {
              "type": "integer",
              "minimum": 1
            },
            "resultPassed": {
              "$ref": "#/$defs/richText"
            },
            "resultFailed": {
              "$ref": "#/$defs/richText"
            }
          },
          "additionalProperties": false
        },
        "x": {
          "$ref": "#/$defs/extension"
        }
      },
      "required": [
        "id",
        "title",
        "blocks"
      ],
      "allOf": [
        {
          "if": {
            "properties": {
              "type": {
                "const": "quiz"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "required": [
              "quiz"
            ],
            "properties": {
              "blocks": {
                "minItems": 1,
                "items": {
                  "properties": {
                    "type": {
                      "const": "question"
                    }
                  }
                }
              }
            }
          },
          "else": {
            "properties": {
              "quiz": false
            }
          }
        }
      ],
      "additionalProperties": false
    },
    "completionRule": {
      "type": "object",
      "properties": {
        "type": {
          "enum": [
            "progress",
            "lessons",
            "minScore",
            "mandatoryQuestions",
            "packagePassed"
          ]
        }
      },
      "required": [
        "type"
      ],
      "allOf": [
        {
          "if": {
            "properties": {
              "type": {
                "const": "progress"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Mindestanteil abgeschlossener Lektionen oder Blöcke.",
            "properties": {
              "unit": {
                "enum": [
                  "lessons",
                  "blocks"
                ]
              },
              "minPercent": {
                "$ref": "#/$defs/percent"
              }
            },
            "required": [
              "unit",
              "minPercent"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "lessons"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Bestimmte Lektionen müssen abgeschlossen sein.",
            "properties": {
              "lessonIds": {
                "type": "array",
                "items": {
                  "$ref": "#/$defs/lessonId"
                },
                "minItems": 1,
                "uniqueItems": true
              }
            },
            "required": [
              "lessonIds"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "minScore"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Mindestpunktzahl in Prozent.",
            "properties": {
              "minPercent": {
                "$ref": "#/$defs/percent"
              },
              "scope": {
                "oneOf": [
                  {
                    "type": "object",
                    "description": "Alle bewerteten Fragen des Kurses.",
                    "properties": {
                      "kind": {
                        "const": "allScored"
                      }
                    },
                    "required": [
                      "kind"
                    ],
                    "additionalProperties": false
                  },
                  {
                    "type": "object",
                    "description": "Nur diese Quiz-Lektion.",
                    "properties": {
                      "kind": {
                        "const": "lesson"
                      },
                      "lessonId": {
                        "$ref": "#/$defs/lessonId"
                      }
                    },
                    "required": [
                      "kind",
                      "lessonId"
                    ],
                    "additionalProperties": false
                  }
                ]
              }
            },
            "required": [
              "minPercent",
              "scope"
            ]
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "mandatoryQuestions"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Alle Pflichtfragen (mandatory=true) sind richtig beantwortet.",
            "properties": {}
          }
        },
        {
          "if": {
            "properties": {
              "type": {
                "const": "packagePassed"
              }
            },
            "required": [
              "type"
            ]
          },
          "then": {
            "type": "object",
            "description": "Ein eingebettetes HTML-Paket meldet »bestanden«.",
            "properties": {
              "blockId": {
                "$ref": "#/$defs/blockId"
              }
            },
            "required": [
              "blockId"
            ]
          }
        }
      ],
      "unevaluatedProperties": false
    }
  }
}