<!--
  Dieser Skill ist eine der Arbeitsanweisungen, mit denen die Filme auf
  caligatus.eu entstanden sind. Er liegt hier so, wie er im Einsatz ist — bis
  auf Pfade und Zugangsdaten, die durch Platzhalter ersetzt wurden.

  Ablage: ~/.claude/skills/<name>/SKILL.md
  Zugang: die Umgebungsvariable KIE_API_KEY muss gesetzt sein.
-->

---
name: schulung
description: >
  Schulungs-, Erklär- und Anschauungsvideos produzieren, in zwei Ausgabeformen: als
  fertiges MP4 oder als interaktive Lerneinheit in einer offline lauffähigen HTML-Datei.
  Mit KI-Videos (Seedance über kie.ai), Erklär-Animationen (HyperFrames), Voiceover
  (ElevenLabs über kie.ai), Musik und Geräuschen (Suno), KI-Bildern, Untertiteln und
  optional Gamification und Quiz. Für Unternehmen (Mitarbeiterschulung, Compliance,
  Onboarding), Coaches, Lehrkräfte und Kursanbieter. Nutzen bei: "Schulungsvideo",
  "Erklärvideo", "Anschauungsvideo", "Lernvideo", "interaktive Schulung", "E-Learning".
---

# /schulung — Schulungsvideos und Lerneinheiten mit kie.ai + HyperFrames

Produziert Schulungs- und Erklärmaterial in **zwei Ausgabeformen**. Die Medienproduktion ist
in beiden identisch, nur Anfang und Ende unterscheiden sich.

## ⚠ ZUERST klären: welche Ausgabeform?

Das ist die erste Frage an den User, noch vor allem anderen. Beide Formen heißen im Alltag
„Schulungsvideo", sind aber verschiedene Produkte mit verschiedenem Aufwand.

| | **MODUS VIDEO** | **MODUS LERNEINHEIT** |
|---|---|---|
| Ergebnis | eine MP4-Datei | eine offline lauffähige HTML-Datei |
| Aufbau | linear, durchlaufend | Level, Quiz, XP, Fortschrittsspeicherung |
| Teil 1 heißt | `storyboard.md` | `curriculum.md` |
| Nach der Produktion | Schnitt, Musik, Untertitel | HTML bauen, Browser-Test |
| Verteilung | LMS, Intranet, Mail, YouTube | Mail, Drive, LMS als Datei |
| Aufwand | geringer | deutlich höher |
| Wann | der Regelfall; das erwarten die meisten | wenn Interaktion und Nachweis gebraucht werden |

**Im Zweifel MODUS VIDEO.** Wer „Schulungsvideo" sagt, meint fast immer ein Video. Die
Lerneinheit lohnt erst, wenn Abfragen, Entscheidungsszenarien oder ein Wissenscheck wirklich
gebraucht werden — etwa bei Pflichtunterweisungen mit Teilnahmenachweis.

Beide Modi teilen sich die Phasen 3 bis 8. Danach trennt es sich.

**Voraussetzungen:** `media-skill` installiert und `KIE_API_KEY` gesetzt (den Schlüssel dort ablegen, wo Sie Zugangsdaten
verwahren — nicht im Projektordner), HyperFrames-Skills installiert
(`npx skills add heygen-com/hyperframes`), ffmpeg, Node 22+. Whisper
(`pip install openai-whisper`) nur als Rückfallebene, siehe Phase 4.

## ⚠ Pflicht: Jedes Video wird als KI-generiert gekennzeichnet

**Ohne Ausnahme, unabhängig vom Stil.** Auch ein erkennbarer Trickfilm bekommt den Hinweis.

Der Grund ist nicht das Bild, sondern **die Stimme**. Ein synthetischer Sprecher klingt heute
von einem echten nicht zu unterscheiden. Ob das unter Art. 50 KI-VO fällt, ist nicht sauber
ausjudiziert — die Stimme ahmt keinen bestimmten Menschen nach und ist damit kein Deepfake im
engeren Sinn, aber die Frage steht offen. Solange sie offen ist, wird gekennzeichnet.
Entscheidung vom 16.08.2026, gilt bis auf Weiteres.

**Die Gestaltung ist festgelegt und nicht je Produktion neu zu erfinden:**

| | |
|---|---|
| Text | `KI-generiert` |
| Ort | oben rechts |
| Größe | Bildhöhe geteilt durch 34, mindestens 14 px |
| Abstand zum Rand | Bildhöhe geteilt durch 26 |
| Zeitpunkt | ab 0,3 s, 0,4 s einblenden, 2,0 s stehen, 0,4 s ausblenden |
| Aussehen | weiße Fettschrift auf schwarzem Kasten mit 45 % Deckung |

Umgesetzt wird das **am fertigen Master**, als letzter Schritt vor der Auslieferung, damit es
in jeder abgeleiteten Fassung steckt. Bei einer Lerneinheit bekommt **jedes** Level-Video den
Hinweis, nicht nur das erste — die Level werden einzeln abgespielt.

**Dafür liegt ein fertiges Skript bereit, das nicht jedes Mal neu gebaut wird:**

```bash
python3 ~/.claude/skills/schulung/scripts/kennzeichnung.py fertig/*.mp4
```

Es rechnet die Maße aus der Bildhöhe, legt das Original als `<name>-ohne-hinweis.mp4`
daneben und ist damit umkehrbar. Der Aufruf dahinter, falls er von Hand gebraucht wird:

```bash
ffmpeg -i master.mp4 -vf "drawtext=fontfile='/System/Library/Fonts/Supplemental/Arial Bold.ttf':\
text='KI-generiert':fontsize=32:fontcolor=white:box=1:boxcolor=black@0.45:boxborderw=12:\
x=w-tw-42:y=42:alpha='if(lt(t,0.3),0,if(lt(t,0.7),(t-0.3)/0.4,if(lt(t,2.7),1,if(lt(t,3.1),(3.1-t)/0.4,0))))'" \
  -c:v libx264 -crf 18 -preset slow -c:a copy fertig.mp4
```

Maße gelten für 1080p. Bei anderer Höhe `fontsize`, `boxborderw` und die Ränder mitskalieren.

**Das ersetzt nicht die Stilregel.** Kein Fotorealismus bleibt richtig, weil ein
fotorealistischer Sprecher einen ganzen weiteren Pflichtenkreis auslöst. Die Kennzeichnung ist
die zusätzliche Absicherung, nicht ihr Ersatz.

## Alles Mediale läuft über `media-skill`

Bilder, Videos und Sprache werden **ausschließlich** über kie.ai erzeugt, und zwar über die
Skripte des `media-skill`. Dieser Skill hier beschreibt, *was* produziert wird; *wie* ein
Auftrag aussieht, steht dort:

- `~/.claude/skills/media-skill/SKILL.md` — Ablauf, Guthaben, Ablage, Galerie
- `~/.claude/skills/media-skill/references/modelle.md` — Eingabefelder je Modell und die Fallen
- `~/.claude/skills/media-skill/references/preise.json` — **die einzige Preisquelle**

**In dieser Datei steht bewusst keine einzige Preiszahl.** Eine zweite Preistabelle wäre nach
der ersten Änderung bei kie.ai falsch, ohne dass es jemand merkt. Gerechnet wird immer:

```bash
python3 ~/.claude/skills/media-skill/scripts/kie.py preise --beim-start
python3 ~/.claude/skills/media-skill/scripts/kie.py preis <modell> …
python3 ~/.claude/skills/media-skill/scripts/kie.py guthaben
```

**Vor jedem Videoauftrag `references/modelle.md` lesen.** Die Eingabefelder unterscheiden sich
zwischen den Modellen, und kie.ai ändert sie gelegentlich.

Weil dieser Skill die Modelle fest vorgibt, gilt der Fall „Modell genannt" aus dem
`media-skill`: keine Modellberatung, keine Alternativenliste, nur die Kostenzeile und los.

## Der Ablauf hat zwei Teile — niemals vermischen

| | **TEIL 1 — Konzept** | **TEIL 2 — Produktion** |
|---|---|---|
| Ergebnis | `storyboard.md` bzw. `curriculum.md` | die fertige MP4- oder HTML-Datei |
| Kosten | keine | wenige Euro, siehe Produktionsschätzung |
| Dauer | Minuten | ~1 Stunde |
| Dazwischen | **Freigabe-Gate: explizites „Go" des Users abwarten** | |

**Warum getrennt — und zwar nicht wegen des Geldes.** Die Credits sind seit der Umstellung
auf kie.ai kein Argument mehr: Eine Sekunde Film kostet rund 1,5 Cent, eine ganze Schulung
liegt im niedrigen einstelligen Eurobereich. Das Gate bleibt trotzdem richtig, aus zwei
anderen Gründen.

**Zeit.** Eine Textänderung in Teil 1 ist eine Minute. Dieselbe Änderung nach der Produktion
zieht neues Voiceover, neue Zeitstempel, neu gesetzte Beats, neuen Render und neues Muxing
nach sich — automatisiert ist davon alles außer der Zuordnung Wort zu Beat, und der Durchlauf
dauert rund eine Stunde.

**Freigabe.** Der Inhalt muss oft von Dritten abgenommen werden (Recht, Compliance, Kunde,
Fachabteilung). Die lesen ein Dokument, kein fertiges Video, und sie kommentieren Text sehr
viel bereitwilliger als eine fertige Produktion.

**Bringt der User bereits Material mit** (Skript, Kurskonzept, Foliensatz, Richtlinie), wird
Teil 1 zum Prüfen und Umbauen: Inhalt auf Level-Struktur mappen, Lücken benennen, Medienplan
und Interaktionen ergänzen. Nicht neu erfinden, was schon da ist.

---

# TEIL 1 — KONZEPT (keine Credits)

## Phase 0 — Briefing (per AskUserQuestion, alle vier Fragen auf einmal)

Ohne diese Angaben nicht starten — sie bestimmen Umfang, Ton und Produktionskosten.

0. **Ausgabeform** — Video oder interaktive Lerneinheit? Siehe die Weiche ganz oben. Wenn
   der User nichts dazu sagt, MODUS VIDEO annehmen und es im Konzept sichtbar machen.
1. **Thema & Lernziele** — Worum geht es, und was sollen die Lernenden danach können bzw.
   anders machen? Gibt es vorhandenes Material als Grundlage?
2. **Zielgruppe & Vorwissen** — Mitarbeitende / Coaching-Klienten / Schüler:innen /
   Studierende / Kurskäufer; Einsteiger, Fortgeschrittene oder gemischt?
3. **Sprache** — Sprache aller Texte, Stimmen und Bildschirmtexte. Keine Annahme treffen.
4. **Dauer** — bestimmt die Level-Anzahl (Tabelle unten).

**„State, don't ask" (nennen, nicht fragen):** Ansprache leitet sich aus der Zielgruppe ab —
locker/duzend für Coaching, Kurse und Schulungen; formell/siezend für Compliance und
regulierte Branchen. Ebenso: 16:9-Videos, dunkles Design mit Akzentfarbe, XP + Level-Badges.
Im Curriculum sichtbar machen, damit der User widersprechen kann.

### Dauer → Struktur

**MODUS VIDEO:** Faustregel 2,5 Wörter je Sekunde Sprechtext. Drei Minuten Video sind also
rund 450 Wörter und je nach Schnitt 20 bis 30 Clips. Über acht Minuten wird es zäh; dann
lieber in mehrere Videos teilen.

| Gewünschte Dauer | Szenen | Sprechtext |
|---|---|---|
| ~90 s (Kurzerklärung) | 4–6 | ~220 Wörter |
| ~3 Min (Standard) | 8–12 | ~450 Wörter |
| ~5–8 Min (ausführlich) | 15–20 | ~750–1200 Wörter |

**MODUS LERNEINHEIT** (Level = 1 Video/Animation + 1 Interaktion):

| Gewünschte Dauer | Level | Voiceover je Szene |
|---|---|---|
| ~10–15 Min (Kompakt-Lektion) | 3–4 | ~25–35 s |
| ~20–30 Min (Standard-Modul) | 5–6 | ~30–40 s |
| ~30–45 Min (volle Schulung) | 7–8 | ~35–45 s |
| 60+ Min (Kurs) | in Module à 6–8 Level teilen, je eine HTML-Datei | — |

Faustregel: Ein Level kostet die Lernenden ~4–6 Minuten. Bei 60+ Min NICHT eine Riesendatei
bauen — mehrere Modul-Dateien plus Startscreen mit Modulübersicht.

### Sprachregeln (bei jeder Sprache gleich)

- **Lernenden-Texte** (Voiceover, Bildschirmtexte, Quiz, Feedback) in der Zielsprache.
- **Bild- und Video-Prompts IMMER auf Englisch.** Die Videomodelle auf kie.ai ignorieren
  deutsche Prompts stillschweigend, es kommt einfach etwas anderes heraus. Dazu „no readable
  text, no captions" in den Prompt, damit kein falschsprachiger Text im Bild landet.
- **Stimme muss zur Sprache passen:** Kandidaten vorhören, mit einem Test-Sample prüfen
  (Phase 4) und `language_code` auf die Zielsprache setzen.
- **Layout je Sprache prüfen:** Deutsch/Finnisch haben lange Komposita, die Titel sprengen;
  Spanisch/Französisch brauchen mehr Zeilen.

## Phase 1 — Recherche & Stoffsammlung

- Bei Fach-, Rechts- und Compliance-Themen: **aktuellen Stand recherchieren** (WebSearch).
  Gesetze und Standards ändern sich; Quellen mit Datum notieren.
- Vorhandenes Material des Users sichten und als Primärquelle behandeln.
- Aussortieren: Was ist wirklich handlungsrelevant für diese Zielgruppe? Lieber 5 Dinge, die
  sitzen, als 15 zum Vergessen.

## Phase 2 — Konzeptdokument schreiben

Als `curriculum.md` im Projektordner anlegen. Es ist ein eigenständiges Dokument, das auch
ohne die spätere HTML-Datei Sinn ergibt und weitergereicht werden kann.

**Story-Rahmen mit Guide-Figur:** ein abstraktes Objekt wählen (leuchtender Orb, Kristall,
Roboterwürfel) — NIEMALS ein Mensch, weil abstrakte Objekte über alle KI-Generationen hinweg
konsistent bleiben. Die Figur spricht die Lernenden direkt an.

**⚠ Alle vorkommenden Figuren im Dokument festnageln**, nicht nur die Guide-Figur. „Eine
Person" reicht nicht: Das Modell füllt die Lücke selbst und tut es nicht zweimal gleich. In
einer Reihe von 28 Bildern entstand so 27 Mal ein Mann und einmal eine Frau. Bei zwölf Clips
wechselt sonst die Hauptfigur mehrfach. Also festlegen: Geschlecht, Alter, Haare, Kleidung,
und diese Beschreibung in **jedem** Bildprompt wortgleich wiederholen.

### MODUS VIDEO: Aufbau von `storyboard.md`

1. **Steckbrief** — Thema, Zielgruppe, Sprache, Länge, Ansprache, Stil, Figuren, Stand
2. **Kernbotschaft** — der eine Satz, den die Zuschauer behalten sollen
3. **Sprechtext am Stück**, in der Zielsprache. Das ist das eigentliche Dokument, denn er
   bestimmt alles Weitere. Er wird als Ganzes freigegeben, nicht in Häppchen.
4. **Szenentabelle**: Szene | Sprechtext-Abschnitt | Bildidee | Medium | geplante Länge
5. **Pro Szene** der englische Bildprompt für das Referenzbild und ein Satz zur Bewegung
6. **Ton** — Musikstimmung, gewünschte Geräusche
7. **Quellen & Stand** bei Fachthemen
8. **Produktionsschätzung**

**Die Clip-Aufteilung ist hier noch vorläufig.** Endgültig wird sie erst nach Phase 4, wenn
die Vertonung gemessen vorliegt. Im Storyboard steht eine Schätzung, keine Festlegung.

### MODUS LERNEINHEIT: Aufbau von `curriculum.md`

1. **Steckbrief** — Thema, Zielgruppe, Sprache, Dauer, Ansprache, Guide-Figur, Stand/Datum
2. **Lernziele** — übergeordnet plus eines pro Level, formuliert als „Die Lernenden können …"
3. **Level-Übersicht** als Tabelle: Level | Lernziel | Merksatz | Medium | Interaktion
4. **Pro Level ausführlich:**
   - Lernziel und **Merksatz** (der eine Satz, der hängenbleiben soll)
   - **Lehrtext** — die eigentliche fachliche Substanz in Prosa. Das ist der Kern des
     Dokuments und die Grundlage für Voiceover und Bildschirmtexte.
   - **Voiceover-Skript** in der Zielsprache (Wortzahl zur Zieldauer: ~2,5 Wörter/Sekunde)
   - **Medienplan** — genau eine Festlegung pro Level:
     - `FILM` → Seedance-Prompt (Englisch) + geplante Shot-Längen
     - `ANIMATION` → HyperFrames-Beat-Plan (welches Element erscheint zu welcher Aussage)
     - optional `BILD` → Bild-Prompt (Englisch) für den Interaktions-Screen
   - **Interaktion vollständig ausformuliert** — Fragen, Optionen, Auflösungen, Feedbacktexte, XP
5. **Abschluss-Check** — alle Fragen mit richtiger Antwort und Ablenkern
6. **Zusammenfassung / Merkblatt** — alle Merksätze für den Abschluss-Screen
7. **Quellen & Stand** — bei Fachthemen Pflicht
8. **Produktionsschätzung** — Credits und Euro, ausgerechnet mit `kie.py preis` (siehe unten)

**Medium richtig wählen** (bestimmt Kosten und Qualität):

| Inhalt | Medium |
|---|---|
| Story-Moment, Emotion, Menschen in Situationen | FILM (Seedance) — der einzige echte Kostenblock, sparsam einsetzen |
| Konzepte, Listen, Modelle, Regeln, Prozesse, Zahlen | ANIMATION (HyperFrames) — kostenlos, scharfer Text |
| Kontext für einen Interaktions-Screen | BILD (gpt-image-2) — sehr günstig |

Richtwert: 2–3 Filme pro Schulung, alles andere Animation.

### Interaktions-Baukasten (pro Level eine ANDERE Form — Abwechslung ist der Punkt)

| Interaktion | Wofür |
|---|---|
| Selbsteinschätzung (Slider) | Onboarding, Vorwissen abholen, Personalisierung |
| Vorhersage-Spiel mit Wahrscheinlichkeits-Balken | Aha-Momente, Intuition vs. Realität |
| Irrtum-oder-Fakt-Karten (Flip) | Fakten vs. verbreitete Irrtümer |
| „Finde die N Fehler" (Sätze/Elemente anklicken) | Kritisches Prüfen, Fehlersuche |
| Drag & Drop in Kategorien | Klassifizierungen, Hierarchien, Zuordnungen |
| Szenario-Quiz mit 2 Buttons | Binäre Unterscheidungen |
| Klickbare Zeitleiste | Abläufe, Daten, Meilensteine |
| Branching-Story (3 Optionen, Konsequenz-Feedback) | Alltagsentscheidungen, Verhalten |
| Rapid-Fire mit Timer | Do's & Don'ts, schnelles Urteil |
| Sortier-/Reihenfolge-Aufgabe | Prozesse, Schritt-für-Schritt-Abläufe |
| Abschluss-Check (8–10 Fragen, gemischt) | Wissenssicherung am Ende |

**Abschluss:** Zusammenfassungs-Screen mit den Merksätzen aller Level, erreichten XP und
Ergebnis des Abschluss-Checks, plus druckbarem Merkblatt (Print-CSS). Ein Zertifikat ist
standardmäßig NICHT Teil der Schulung; nur bauen, wenn der User es ausdrücklich verlangt.

### Die Produktionsschätzung ausrechnen, nicht schätzen

Erst den Preisabgleich laufen lassen, dann jeden Posten einzeln rechnen und aufsummieren:

```bash
python3 ~/.claude/skills/media-skill/scripts/kie.py preise --beim-start
python3 ~/.claude/skills/media-skill/scripts/kie.py preis gpt-image-2-text-to-image --qualitaet 2k --anzahl <bilder+2>
python3 ~/.claude/skills/media-skill/scripts/kie.py preis bytedance/seedance-1.5-pro --qualitaet 720p --sekunden <summe aller shot-laengen>
python3 ~/.claude/skills/media-skill/scripts/kie.py preis google/gemini-2-5-pro-tts --zeichen <zeichen aller voiceover-skripte>
```

Die HyperFrames-Animationen kosten nichts, sie rendern lokal. Der Videoanteil dominiert die
Summe deutlich, Bilder und Stimmen fallen kaum ins Gewicht. Vor der Produktion einmal
`kie.py guthaben` prüfen.

## ⛔ Gate 1 — der Stil wird einzeln abgenommen

> [!important] Ausnahme: laufende Reihen
> Hat ein Auftraggeber für einen bestimmten Zielort bereits mehrere Stücke im selben Stil
> veröffentlicht, ist die Stilfrage **beantwortet**. Sie dann erneut zu stellen, ist keine
> Sorgfalt, sondern ein Zeichen, dass die Vorgeschichte nicht gelesen wurde.
> **Prüfe zuerst, ob es schon eine Reihe gibt** — dann Stil-Block übernehmen und im
> Storyboard als Feststellung nennen, nicht als Entscheidung.
> Beispiel: caligatus.eu ist seit 17.08.2026 dauerhaft auf Papercut festgelegt.
> Gate 1 gilt weiterhin für **neue Auftraggeber oder neue Zielorte**.

**Vor dem Konzeptdokument, nicht darin.** Die Stilfrage geht in einer Freigabeliste unter:
Der User liest den Sprechtext, findet ihn gut und sagt „los" — die Stilfrage war Punkt 3 von
sechs und bleibt unbeantwortet. Damit ist die auffälligste Eigenschaft des fertigen Films die
einzige, über die nie jemand entschieden hat. Genau so ist es am 15.08.2026 passiert.

Deshalb **eigener Schritt mit eigener Antwort**:

1. Aus der Stilbibliothek zwei bis drei Kandidaten vorschlagen
   (`Second Brain/04 Ressourcen/…/Stilmuster für Schulungsvideos.md`, 28 Stile mit Bild).
2. **Die Musterbilder mitschicken**, nicht nur die Namen. „Risograph" sagt niemandem etwas,
   das Bild sofort.
3. Je Kandidat einen Satz, warum er zu diesem Stoff passt, und einen, was er erschwert.
4. **Auf eine Nennung warten.** Schweigen ist keine Wahl. Kommt keine Antwort, nachfragen
   statt annehmen.

Gibt es eine Gestaltungsvorgabe des Auftraggebers (Corporate Design, Markenrichtlinie), sticht
sie die Bibliothek. Dann wird der Stil daraus abgeleitet und ebenso vorgelegt.

## ⛔ Gate 2 — Freigabe des Konzepts

Das Konzeptdokument (`storyboard.md` bzw. `curriculum.md`) an den User ausliefern und
**auf ein explizites „Go" warten**. Vorher wird kein einziger Credit ausgegeben. Beim
Übergeben diese Punkte zur Prüfung nennen:

- Deckt der Inhalt die Lernziele ab — fehlt etwas Handlungsrelevantes?
- Stimmen Fakten und Rechtsstand (Quellen genannt)?
- Passen Ansprache und Beispiele zur Zielgruppe?
- Ist die geschätzte Dauer realistisch?
- Ist die Kostenschätzung in Ordnung?

Änderungswünsche im Dokument einarbeiten und erneut vorlegen. Erst nach dem „Go" → Teil 2.

**Eine unbeantwortete Frage ist keine Zustimmung.** Bleibt eine der Freigabefragen offen und
der User sagt trotzdem „los", gilt das für alles, was er beantwortet hat — für den Rest wird
nachgefragt.

---

# TEIL 2 — PRODUKTION (verbraucht Credits)

Ab hier ist `curriculum.md` die verbindliche Quelle. Nicht improvisieren, nicht umformulieren:
was produziert wird, steht im Dokument. Fällt bei der Produktion doch ein inhaltlicher Fehler
auf, erst das Curriculum korrigieren, dann produzieren.

**Ablage:** Alles Erzeugte landet über `kie.py lauf` automatisch in
`~/Medien/JJJJ-MM-TT-<projekt>/` samt `meta.json` und Galerie-Eintrag. Als `--projekt` durchweg
denselben Namen verwenden (z. B. `schulung-datenschutz`), dann liegt die ganze Produktion
beisammen. Für den Schnitt in den Schulungs-Projektordner kopieren, nicht verschieben.

**⚠ Projektnamen produktionsspezifisch wählen, nie nur nach der Funktion.** Wer `platte-02`
nimmt, kollidiert mit der Vorproduktion, die denselben Namen benutzt hat. Ein Skript, das
vorhandene Läufe überspringt, hält die fremden Bilder dann für erledigt und baut sie ein —
ohne eine einzige Fehlermeldung. Am 16.08.2026 genau so passiert. Richtig ist ein Präfix je
Produktion: `wiki-platte-02`, `datenschutz-platte-02`. `kie.py` schreibt Projektnamen
außerdem klein; wer danach mit einem großgeschriebenen Muster sucht, findet nichts.

## Zwei Dinge über URLs, die den ganzen Ablauf bestimmen

1. **Ergebnis-URLs von kie.ai verfallen nach 24 Stunden.** `kie.py` lädt deshalb sofort
   herunter. Nie eine kie.ai-URL irgendwo als dauerhafte Quelle eintragen.
2. **Bild-Eingaben brauchen eine erreichbare URL, keine lokale Datei.** Referenzbild und
   Anschluss-Frames müssen also hochgeladen werden:

```bash
hochladen() {
  curl -s -X POST https://kieai.redpandaai.co/api/file-stream-upload \
    -H "Authorization: Bearer $KIE_API_KEY" \
    -F "file=@$1" -F "uploadPath=schulung/$2" |
  python3 -c "import sys,re;m=re.search(r'\"downloadUrl\"\s*:\s*\"([^\"]+)\"',sys.stdin.read());print(m.group(1) if m else 'FEHLER')"
}
hochladen guide.png datenschutz     # gibt die URL aus
```

**⚠ Der Upload läuft über einen anderen Host als der Rest der API.** `api.kie.ai` kennt diesen
Pfad nicht und antwortet mit HTTP 404, auch wenn die kie.ai-Doku ihn dort führt. Richtig ist
`kieai.redpandaai.co`, geprüft am 15.08.2026. Der Schlüssel ist derselbe.

Diese Upload-URLs halten **drei Tage**, reichlich für eine Produktion an einem Stück, aber
kein Archiv. Kommt `FEHLER` zurück, die rohe Antwort ohne den Python-Teil ansehen: kie.ai
meldet Fehler im Antwortrumpf über `code != 200`, nicht über den HTTP-Status. Ein
`code: 401` heißt, dass `KIE_API_KEY` nicht gesetzt ist.

## Phase 3 — Referenzbild der Guide-Figur (Konsistenz-Anker!)

```bash
python3 ~/.claude/skills/media-skill/scripts/kie.py lauf \
  --modell gpt-image-2-text-to-image --projekt <projekt> --typ bild \
  --input '{"prompt": "<Figur exakt beschreiben: Farben, Form, Details, Umgebung>. No text, no captions, no letters.", "aspect_ratio": "16:9", "resolution": "2K"}'
```

- Figur exakt beschreiben (Farben, Form, Details, Umgebung) plus „no text, no captions".
- Den Auftrag **zweimal** stellen, denn `n` greift hier nicht. Beide Kandidaten ansehen
  und den OHNE eingebrannten Text wählen. GPT Image 2 ist stark im
  Text-Rendering und schreibt deshalb besonders gern den Namen ins Bild; der würde über die
  Bildreferenz in alle Videos durchbluten.
- Das gewählte Bild **hochladen** (Funktion oben) und die URL für den Rest der Produktion
  merken. Sie geht in jeden Video-Auftrag als `input_urls`.

## Phase 4 — Voiceover

**Erste Wahl ist `google/gemini-2-5-pro-tts` mit der Stimme `Charon`.** Sie hat drei
Produktionen getragen und wurde am 17.08.2026 ausdrücklich abgenommen: „einwandfrei, selbst
schwierige Wörter völlig sauber". Dazu kostet sie **rund 40 % von ElevenLabs** — gemessen 15,
16 und 21 Credits, wo für ElevenLabs 34,5, 39 und 38 veranschlagt waren.

```bash
python3 ~/.claude/skills/media-skill/scripts/kie.py lauf \
  --modell google/gemini-2-5-pro-tts --projekt <projekt> --typ audio \
  --input '{"speakers": [{"speaker_id": "Speaker 1", "voice_name": "Charon"}],
            "dialogue_turns": [{"speaker_id": "Speaker 1", "text": "<Abschnitt>"}]}'
```

**Ein Abschnitt je Auftrag.** Das Modell ist auf Dialoge ausgelegt und schneidet mehrere
`dialogue_turns` ab. `speaker_id` muss wörtlich `Speaker 1` heißen.

**Der einzige Nachteil: keine Wort-Zeitstempel.** Untertitel werden dann innerhalb jedes
Abschnitts proportional zur Zeichenzahl verteilt. Für gesprochene Erklärfilme reicht das; die
Abschnittsgrenzen selbst sind ja gemessen. Wer wortgenaue Untertitel braucht — etwa bei einer
Pflichtunterweisung mit Nachweis —, nimmt ElevenLabs oder lässt Whisper über die fertige Datei
laufen.

### ElevenLabs — wenn Wort-Zeitstempel gebraucht werden

Modell `elevenlabs/text-to-speech-multilingual-v2`, höchstens 5.000 Zeichen je Auftrag.

```bash
python3 ~/.claude/skills/media-skill/scripts/kie.py lauf \
  --modell elevenlabs/text-to-speech-multilingual-v2 --projekt <projekt> --typ audio \
  --input '{"text": "<Voiceover aus dem Curriculum>", "voice": "<voice_id>", "language_code": "de", "speed": 1.15, "timestamps": true}'
```

**⚠ `voice` erwartet eine ID, keinen Namen.** `voice_id` als Feldname wird abgewiesen. Geprüfte
deutsche Stimmen: Leon `MJ0RnG71ty4LH3dvNfSd`, Sven `Tsns2HvNFKfGiNjllgqo`,
Benjamin `LruHrtVF6PSyGItzMNHS`.

**⚠ Der Dienst fällt aus.** Über zwei Produktionen hinweg rund zwanzig Stunden am Stück, mit
einem nichtssagenden `Internal Error`. Deshalb ist Gemini nicht die Rückfallebene, sondern der
Regelfall — und ElevenLabs die Ausnahme für den einen Fall, in dem seine Zeitstempel zählen.

**Stimme wählen:** Das Modell bietet rund 70 Preset-Stimmen. Vorhören ohne einen Credit
auszugeben über `https://static.aiquickdraw.com/elevenlabs/voice/<voice_id>.mp3`. Für Deutsch
zuerst die deutsch benannten Stimmen probieren (Lutz, Sven, Felix, Emma, Leon) und dann mit
einem kurzen Test-Sample in der Zielsprache entscheiden. Multilingual v2 spricht jede Stimme in
jeder Sprache, aber der Akzent sitzt nicht bei jeder gleich gut. Die gewählte Stimme für die
ganze Schulung beibehalten.

**⚠ TEMPO-REGEL:** TTS-Erzählstimmen sind fürs Lernen zu langsam. Deshalb `speed` setzen,
erlaubt ist 0,7 bis 1,2. Etwa 1,15 klingt natürlich; Ziel ist normales Sprechtempo, nicht
Hörbuch-Ruhe. Den Wert am Test-Sample verifizieren, je nach Stimme und Sprache passen auch 1,1
oder 1,2. Der native Parameter ist dem früheren ffmpeg-Weg vorzuziehen, weil die
Wort-Zeitstempel dann zur erzeugten Datei passen. Falls doch nachträglich beschleunigt wird:

```bash
ffmpeg -i vo.mp3 -filter:a "atempo=1.15" -b:a 128k vo_fast.mp3
```

**Zeitstempel für die Choreografie:** Mit `"timestamps": true` liefert das Modell die
Wort-Zeiten mit. Nach dem Lauf `kie.py status <taskId>` aufrufen und in `resultJson` nachsehen,
ob die Zeitdaten dort ankommen. Wenn ja, sind sie die Beat-Grundlage für Phase 6 und legen die
endgültigen Szenenlängen fest. Wenn nicht, Rückfallebene Whisper auf der fertigen Datei:

```bash
whisper vo.mp3 --language de --output_format json
```

Wurde nachträglich per ffmpeg beschleunigt, muss Whisper zwingend auf der **beschleunigten**
Datei laufen, sonst passen die Beats nicht.

### Jetzt erst die endgültigen Shot-Längen festlegen

Die Aufteilung aus dem Storyboard war eine Schätzung. Mit den gemessenen Zeiten steht fest,
wie lange jeder Abschnitt wirklich dauert — und **daraus** ergeben sich die Shot-Längen, nicht
umgekehrt. Wer die Clips vorher festzurrt, plant sechs Sekunden für einen Satz, der neun
braucht, und schneidet ihn dann kaputt.

Die Tabelle im Storyboard entsprechend fortschreiben, bevor Phase 5 beginnt.

## Phase 5 — Cineastische Videos (Seedance)

Im **MODUS VIDEO** für alle Szenen, die gefilmt werden sollen. Im **MODUS LERNEINHEIT** nur
für die `FILM`-Level.

### ⚠ Grundregel: nie aus Text, immer aus einem Bild

**Jeder Clip bekommt sein eigenes Referenzbild.** Kein Film-Shot entsteht aus einem
Textprompt allein. Drei Gründe:

- Ein Bild kostet weniger als die Hälfte eines Clips. Am Bild lässt sich iterieren, bis die
  Komposition sitzt; bewegt wird erst der Gewinner.
- Der Stil hält deutlich besser, weil er im Startbild schon festgelegt ist.
- Man sieht vorher, was man bekommt, statt es zu erraten.

Ablauf je Szene: Bild erzeugen (Phase 7) → ansehen → hochladen → als `input_urls` in den
Videoauftrag. Das gilt in **beiden Modi**; Phase 7 kommt damit vor Phase 5, auch wenn die
Nummern anders stehen.

**Standardmodell ist `bytedance/seedance-1.5-pro`**, immer 16:9, ohne Ton, denn die Stimme
kommt aus Phase 4. `bytedance/seedance-2` kostet ein Vielfaches und wird nur nach
ausdrücklicher Ansage des Users eingesetzt.

```bash
python3 ~/.claude/skills/media-skill/scripts/kie.py lauf \
  --modell bytedance/seedance-1.5-pro --projekt <projekt> --typ video \
  --input '{"prompt": "<englischer Prompt + Stil-Block>", "input_urls": ["<Referenzbild-URL>"],
            "duration": 10, "resolution": "720p", "aspect_ratio": "16:9",
            "generate_audio": false, "fixed_lens": true}'
```

### 720p ist der Standard, nicht 1080p

Phase 8 skaliert die eingebetteten Clips ohnehin auf 1280 Pixel Breite herunter, weil der
Player sie nur rund 880 Pixel breit zeigt. 1280 × 720 **ist** 720p. In 1080p zu erzeugen
heißt also, die Hälfte beim Einbetten wieder wegzuwerfen — bei doppeltem Sekundenpreis.

Deshalb: **720p erzeugen, es sei denn, der User will ausdrücklich einen 1080p-Master** zur
Weiterverwendung für Social, LMS oder Präsentationen. Das ist eine Frage, die ins Curriculum
gehört, nicht in die Produktion. Die HyperFrames-Animationen bleiben davon unberührt, die
rendern weiterhin in 1920 × 1080 und kosten nichts.

### Modellwahl nach Shot-Länge, nicht nach Gewohnheit

`veo3.1` rechnet **je Clip** statt je Sekunde, erlaubt aber nur 4, 6 oder 8 Sekunden. Damit
gibt es keinen generellen Sieger, sondern eine Regel:

**Es gibt keine Regel, die man sich merken kann — nur die Rechnung.** Am 17.08.2026
nachgerechnet: Ein Achtsekundenshot kostet bei Seedance 28 Credits, bei Veo Lite pauschal 30.
Damit ist Seedance auch bei genau acht Sekunden günstiger, und die frühere Regel „genau 8 s →
Veo Lite" ist überholt. Sie stammte aus einer Zeit mit anderen Sekundenpreisen, und genau das
wird wieder passieren. Also **immer beide rechnen lassen**, nie aus dem Gedächtnis entscheiden:

```bash
kie.py preis veo3.1 --stufe lite --qualitaet 720p --sekunden 8
kie.py preis bytedance/seedance-1.5-pro --qualitaet 720p --sekunden 8
```

**Der Preis ist aber nicht das einzige Kriterium.** Seedance gilt als besonders stark bei
stilisiertem und anime-nahem Material, Veo als Allrounder ohne Spezialisierung. Hält Veo den
gewählten Stil schlechter, ist die Ersparnis das falsche Argument. Im Zweifel bei Seedance
bleiben und die paar Cent zahlen.

- **`duration` ist eine Zahl und liegt zwischen 4 und 12 Sekunden.** Länge pro Shot bewusst
  wählen, sie ist der einzige nennenswerte Kostenhebel der ganzen Produktion:

  | Shot-Typ | Dauer |
  |---|---|
  | Kurzer Beat, Übergang, Stimmungsbild | 4–6 s |
  | Standard-Szene mit einer Aktion | 8–10 s |
  | Ausdrucksvolle Szene mit Handlungsbogen | 11–12 s |

  Bei Sequenzen die Shot-Längen so planen, dass ihre **Summe knapp über der Voiceover-Länge**
  liegt. Jede überschüssige Sekunde wird weggeschnitten und ist trotzdem bezahlt: 34 s
  Voiceover → 12 + 12 + 11 statt 12 + 12 + 12.
- Identischer Stil-Block in jedem Prompt (Look, Licht, „no readable text, no captions,
  no speech, nobody talking"), denn Seedance kann keinen sauberen Text rendern.

### ⚠ Die Kamera ruhig halten, sonst zerfällt der Stil

**`"fixed_lens": true` ist der Standard**, dazu „the camera does not move at all, locked-off
static shot" im Prompt. Der Grund ist am 15.08.2026 in neun Bewegungsproben gemessen worden:

Je weiter die Kamera fährt, desto mehr Bildinhalt muss das Modell erfinden, den die Vorlage
nicht enthält — und dabei fällt es auf seine Standarddarstellung zurück, also glatter und
generisch illustriert. Bei einer harten Großaufnahme waren nach vier Sekunden Rasterpunkte,
Risokorn und Kreidestaub verschwunden. **Mit festgestellter Kamera hielten dieselben drei
Stile ihren Look vollständig.**

Nicht der Stil entscheidet also, sondern die Kamerabewegung. Das ist die brauchbarere Regel,
weil sie keinen Stil ausschließt:

| | |
|---|---|
| Ruhige oder gar keine Kamerafahrt | jeder Stil trägt |
| Starke Fahrt oder Zoom | nur robuste Stile, und den letzten Frame prüfen |

Braucht eine Szene wirklich eine Fahrt, dann kurz halten (4–6 s) und **den letzten Frame
gegen den ersten prüfen**. Bewegung im Bild ist ohnehin die bessere Lösung als Bewegung der
Kamera: Die Guide-Figur kann sich drehen, Licht kann wandern, Elemente können hereinkommen.

### Die Platte ist der Regelfall, nicht die Ausnahme

**Erzeuge den Grund jeder Animationsszene mit demselben Modell und demselben Stil-Block wie
die Filmszenen. HyperFrames legt nur Schrift und Bewegung darüber.**

Das ist nach drei erprobten Bauweisen die beste, und der Grund ist kein ästhetischer, sondern
ein logischer: Sobald Animation und Film aus **derselben Quelle** kommen, kann zwischen ihnen
kein Stilbruch entstehen. Jede andere Bauweise ahmt den Stil ein zweites Mal mit einem
anderen Werkzeug nach, und genau an dieser Naht sieht man den Film auseinanderfallen.

| Bauweise | Woher der Grund kommt | Bruchgefahr | Kosten |
|---|---|---|---|
| **Platte** (Regelfall) | dasselbe Bildmodell, derselbe Stil-Block | keine | 10 Credits je Szene |
| CSS-Nachbau | von Hand in CSS nachempfunden | hoch, außer bei reinen Druckbildern | 0 |
| Originalgrafik | Kundenbibliothek als SVG | keine, aber nur vorhandene Motive | 0 |

Gemessen am 16.08.2026: 14 Platten kosteten 140 Credits, also 60 Cent, bei einem Film von
insgesamt 1,45 €. Das sind zwar 42 Prozent der Modellkosten, aber in absoluten Zahlen der
Preis eines Kaffees — und dafür wirkt der Film aus einem Guss. **Rechne die Platten also
selbstverständlich ein, statt sie einzusparen.**

Drei Dinge machen die Platte erst brauchbar:

- **Textfreie Zone im Prompt verlangen.** „The lower third of the image is deliberately kept
  empty and unlit for text." Ohne das steht später die Schrift im Motiv.
- **Verbiete, was der Stil sonst herbeizieht — auch im Inneren von Gefäßen.** „Cut-paper
  **diorama**" holt Guckkästen mit Landschaften, dagegen hilft „no scenery, no landscape, no
  miniature scenes" in jedem Plattenprompt. Am 17.08.2026 kam dazu: Zwei Ablagefächer, die
  leer sein sollten, kamen mit Papierblume, Papierhand und Stift **darin** zurück. Ein leeres
  Behältnis muss man ausdrücklich leer verlangen — „Both trays are completely empty, nothing
  lies inside either of them." Danach saß es auf Anhieb.
- **Kein Text in der Platte.** Schrift kommt aus HyperFrames, sonst ist sie unscharf und in
  der falschen Sprache.
- **Schleier hinter die Textzone legen.** Ein weicher Verlauf von `rgba(7,14,32,0.94)` zur
  Transparenz, über die halbe Bildhöhe. Die Platten sind absichtlich unruhig — Faser,
  Schlagschatten, wandernde Lichter — und Schrift verliert dagegen. Am 16.08.2026 gemessen:
  **fünf von sechs Animationen waren im ersten Durchlauf schlecht lesbar**, einmal Amber auf
  einer amberbeleuchteten Karte. Die textfreie Zone allein reicht nicht, weil das Modell sie
  nur ungefähr einhält.

**Prüfe jede Animation am gerenderten Bild, nicht an der Konsole.** Lesbarkeitsfehler
erzeugen keine Meldung. Ein Frame kurz vor Schluss je Szene, nebeneinander montiert, findet
sie in einem Durchgang.

Nur wenn der Stil ein reines Druckbild ist (Risograph, Ligne Claire, Bauhaus), ist der
CSS-Nachbau eine ernsthafte Alternative — dort besteht der Look aus Fläche, Korn und Kontur,
und das kann CSS. Ein beleuchtetes Diorama besteht aus Licht, Tiefe und Materialkante, und
das kann es nicht.

**Dem Videomodell nur menschliche Bewegung geben.** Kopfdrehung, Griff, Blickwechsel — das
hält, weil die Vorlage die Figur eindeutig vorgibt. Grafische Elemente dagegen (eine Kugel,
ein Pfeil, ein Diagramm) erfindet das Modell beim Animieren neu und macht sie plastisch: Am
16.08.2026 wuchs eine flache Kreisfläche über acht Sekunden merklich an, wanderte und bekam
eine Oberflächenstruktur — trotz `fixed_lens: true`. Grafik gehört deshalb in HyperFrames,
wo sie exakt bleibt und nichts kostet.

**Druckeffekte nur auf Displaygröße.** Ein Passerversatz, ein Farbschatten oder eine zweite
Farbebene liest sich ab etwa 80 Pixeln Schrifthöhe als Druckbild — darunter als Unschärfe.
Bei Fließtext, Definitionszeilen und Beschriftungen also weglassen, sonst wirkt die Schrift
schlicht falsch gerendert. Am 16.08.2026 an einer fertigen Produktion aufgefallen.

**Erfinde keine Sachgebiete, die der Sprechtext nicht nennt.** Ein Beispielwert im Bild
(„max. 60 °C") zieht ein ganzes Umfeld herein, das der Zuschauer nirgends untergebracht
bekommt, und wirkt sofort willkürlich. Nimm Begriffe, die im Sprechtext ohnehin vorkommen —
die brauchen keine Einführung und meist auch keine Einheit.

**Zwei Stile sind trotzdem ungeeignet:** Pixel Art und Voxel. Ihr Raster ist nicht global,
sondern klebt am Objekt — skaliert etwas im Bild, bekommt es gröbere Pixel als der
Hintergrund, und die Rasterlogik zerfällt. Als Standbild auf einem Interaktions-Screen
bleiben beide tadellos.

**Und selbst bei fester Kamera wandert der Bildinhalt.** Gegenstände kommen dazu oder
verschwinden, Haltungen ändern sich. Ein Shot darf sich nicht darauf verlassen, dass ein
bestimmter Gegenstand an seinem Platz bleibt. Muss etwas Bestimmtes gezeigt werden, gehört
es ins **Bild**, nicht in den Film.
- `input_urls` nimmt bis zu zwei Bilder. Für diesen Ablauf genau **eines** übergeben, nämlich
  das Referenzbild bzw. den Anschluss-Frame. Wer zwei braucht, liest vorher die Modellseite.
- Vor der Sequenz beide Modelle rechnen lassen (siehe Längenregel oben) und `kie.py guthaben`
  prüfen.

**⚠ LÄNGE-REGEL: Voiceover länger als 12 s → Shots VERKETTEN, NIEMALS loopen.**
Boomerang-Loops (vor/zurück) sehen kaputt aus, die Figur verschwindet und taucht wieder auf.
Stattdessen nahtlose Sequenz:

1. Letzten Frame extrahieren: `ffmpeg -sseof -0.1 -i clip.mp4 -frames:v 1 last.jpg`
2. Frame hochladen (Funktion oben) → URL
3. Nächsten Shot mit dieser URL als `input_urls` generieren, Prompt beginnt mit
   „SHOT: continuing seamlessly from the start frame — …" (Kamera/Handlung weiterführen)
4. Shots per ffmpeg concat (vorher auf einheitliche fps/Auflösung normalisieren), auf
   VO-Länge + 1 s trimmen; fehlt < 1,5 s, letzten Frame einfrieren (`tpad=stop_mode=clone`).
   So entsteht z. B. ein 34-s-Intro aus 3 Shots (12+12+11).
- Dramaturgie nutzen: Die Sequenz darf einen Bogen erzählen (Warnung → Zögern → Entwarnung).

## Phase 6 — Erklär-Animationen (HyperFrames)

> [!tip] Whiteboard-Stil läuft über einen eigenen Skill
> Soll eine Hand die Zeichnung vor der Kamera Strich für Strich malen, dann **`/whiteboard`**
> aufrufen statt die Animation hier von Hand zu bauen. Der Skill gewinnt aus jeder
> Strichzeichnung die Zeichenpfade, bringt beide Hände fertig mit und rechnet die Zeiten aus
> den Szenendauern. Ton, Musik, Schnitt und Kennzeichnung bleiben hier.
> **Die Szenendauer gibt er vorgegeben**, genau wie jede andere Animation: erst vertonen,
> dann die Längen setzen.

Im **MODUS VIDEO** für alle Szenen, die Konzepte, Listen oder Zahlen zeigen. Im **MODUS
LERNEINHEIT** für alle `ANIMATION`-Level. Der Ton kommt in beiden Fällen aus Phase 4.

Konzepte, Listen, Modelle, Regeln, Prozesse: als HTML/CSS/GSAP-Komposition bauen und zu MP4
rendern. Gestochen scharfer Text in JEDER Sprache (kann Seedance nicht), beat-genau zur Stimme,
und ohne einen einzigen Credit.

- Pro Szene ein Ordner mit `index.html` nach dem HyperFrames-Kontrakt (`/hyperframes-core`):
  Root mit `data-composition-id/-width/-height/-duration` (**1920×1080**, damit die Animationen
  die volle Auflösung der HTML-Datei tragen; Dauer = Voiceover + 1 s), mindestens ein `class="clip"`,
  EIN pausiertes GSAP-Timeline auf `window.__timelines["<id>"]`. Schriftgrößen und Abstände
  gegenüber einem 720p-Layout um Faktor 1,5 skalieren.
- **Design = Look der Schulung:** dunkler Hintergrund, eine Akzentfarbe, Guide-Figur als
  CSS-Element (Kreis mit radial-gradient + Glow + Ring) in jeder Szene → Markenklammer.
- **Beats aus den Zeitstempeln** aus Phase 4: Wenn die Stimme Punkt 3 nennt, erscheint GENAU
  DANN Punkt 3. Elemente per `tl.to/from` an die Segment-Startzeiten setzen.
- **Fachwörter gehören als Definitionszeile ins Bild, nicht doppelt in den Sprechtext.** Wenn
  ein Begriff fällt, der erklärt werden muss, erscheint auf genau diesem Wort eine kurze Zeile
  der Form „Begriff = knappe Erklärung". Der Sprechtext beschreibt die Sache ohnehin; sie
  zusätzlich zu definieren klingt nach Lexikon. Geschrieben nimmt es aber auch mit, wer nur
  halb zuhört, und es bleibt hängen. Genau dafür sind die Animationsszenen da: Schrift, die
  in jeder Sprache scharf ist.
- Stolperfallen (erspart Lint-Runden):
  - Initialzustände mit `gsap.set(...)` VOR der Timeline, nie `tl.set(..., 0)`
  - kein `repeat: -1` (endliche Wiederholungen), keine Uhr, kein Random
  - Titel einzeilig halten (`white-space: nowrap`), Umbrüche kollidieren mit Inhalten
  - Elemente nicht überlappen lassen, `npx hyperframes check` prüft Layout + WCAG-Kontrast
- **Assets wurzelrelativ verlinken, nicht relativ zur Komposition.** Eine Komposition unter
  `compositions/` bekommt `papier.css` und `assets/platte-02.png`, nicht `../papier.css` und
  `../assets/…`. Der Renderer löst die `../`-Fassung gegen die Datei auf und rendert
  anstandslos; die Studio-Vorschau und andere Konsumenten lösen gegen die Projektwurzel auf und
  bekommen 404. Am 17.08.2026 aufgefallen, in einer Vorproduktion steckt der Fehler noch.
- **Mit `--strict` rendern.** Sonst sind Lint-Befunde wie der obige nur Text im Protokoll, den
  niemand liest, und der Render läuft trotzdem durch.
- **Setz die Kleinzeile dorthin, wo die Stimme sie sagt.** Ohne Wort-Zeitstempel geht das über
  die Zeichenposition ihres Ankersatzes im Abschnitt:
  `Vorlauf + Position / Länge × Abschnittsdauer`. Begrenzt auf das Fenster von 2,4 s bis
  Abschnittsdauer minus 4 s, sonst kommt die Zeile zu früh oder steht zu kurz. Feste Zeiten für
  alle Szenen sehen bei einer 19-Sekunden-Szene aus wie ein Standbild: Nach vier Sekunden steht
  alles im Bild, danach passiert nichts mehr.
- Loop: `npx hyperframes lint` → fixen → `npx hyperframes check` → `render --quality draft`
  (Frames extrahieren und ansehen!) → `render --quality high --strict --output final.mp4`
- Ergebnis wiegt ~4–8 MB pro 40-s-Szene in 1080p.

## Phase 7 — Bilder

**Läuft in Wahrheit VOR Phase 5**, denn jeder Filmclip startet auf einem Bild (siehe die
Grundregel dort). Zusätzlich im **MODUS LERNEINHEIT** für die Interaktions-Screens.

```bash
python3 ~/.claude/skills/media-skill/scripts/kie.py lauf \
  --modell gpt-image-2-text-to-image --projekt <projekt> --typ bild \
  --input '{"prompt": "<englischer Prompt + Stil-Block>. No readable text, no faces.", "aspect_ratio": "16:9", "resolution": "2K"}'
```

Bilder machen die Quiz-Screens lebendig, in derselben Bildwelt wie die Videos:
- Startscreen-Hero (das Referenzbild wiederverwenden, kostenlos)
- eine Illustration PRO Entscheidungsszenario
- Vergleichs-Panels für Gegenüberstellungen
- Header-Bild für Suchspiele, Abschlussbild für den Zusammenfassungs-Screen
- Immer: Stil-Block der Videos plus „no readable text, no faces", sonst landen (oft
  falschsprachige) Beschriftungen im Bild
- **`aspect_ratio` immer ausdrücklich setzen.** Ohne Angabe steht es auf `auto`, und dann
  entscheidet das Modell selbst — bei einer Serie bekommt man gemischte Formate. Das Feld
  heißt `aspect_ratio`, das Auflösungsfeld `resolution`; ein `image_size` gibt es nicht, es
  wird stillschweigend ignoriert.
- `2K` für Hero- und Szenario-Bilder. Reine Deko im Hintergrund geht auch mit
  `nano-banana-2-lite` in 1K und kostet dann fast nichts.
- **`gpt-image-2` ignoriert `n`** und liefert immer genau ein Bild. Für zwei Kandidaten
  also zwei Aufträge. Das betrifft auch das Referenzbild in Phase 3.
- Komprimieren: `ffmpeg -i in.png -vf "scale=1024:-2" -q:v 4 out.jpg` → ~80 KB/Bild

## Phase 8 — Muxing (ffmpeg)

- Animationen (exakt VO+1 s lang): Video kopieren, Audio padden:
  `-filter_complex "[1:a]apad[a]" -map 0:v -map "[a]" -t <videodauer> -c:v copy -c:a aac -b:a 96k -ac 1 -movflags +faststart`
- Film-Sequenzen: concat → trim auf VO+1 s → `-c:v libx264 -crf 27 -pix_fmt yuv420p`
- **Größe managen:** Die Filmclips kommen bereits in 720p, also genau in Einbettungsbreite;
  da ist nichts mehr herunterzuskalieren. Nur wenn der User einen 1080p-Master bestellt hat,
  bleibt der in `~/Medien/` liegen und die Embed-Kopie entsteht mit `-vf "scale=1280:-2"`.
  Die HyperFrames-Animationen rendern in 1920 × 1080 und werden ebenso eingebettet.
- Ziel: ≤ 5 MB pro eingebettetem Clip, Gesamtdatei ≤ 50 MB.

## Phase 8b — Musik und Geräusche (Suno über kie.ai) — optional, beide Modi

Musik ist kein Muss, hebt ein Video aber spürbar. Geräusche oft noch mehr: ein Klick, ein
Signalton beim Aha-Moment, ein Türgeräusch tragen in Erklärvideos mehr als jede Melodie.

**⚠ Suno läuft über einen anderen Endpunkt als alles andere** und ist im `media-skill` bisher
nur als Falle dokumentiert, nicht erprobt. Vor dem ersten Einsatz `references/modelle.md`
lesen: `POST /api/v1/generate` statt `/jobs/createTask`, Status über
`/api/v1/generate/record-info?taskId=`.

- **Instrumental verlangen** (`instrumental: true`). Gesang unter Sprache ist unbrauchbar.
- **Leise mischen.** Unter Sprache gehört Musik 18 bis 22 dB unter das Voiceover, sonst
  frisst sie die Verständlichkeit. Im Vorspann und Abspann darf sie hoch.
- Generierte Musik hat einen Vorteil, der oft übersehen wird: **keine Lizenzfrage**. Bei
  Material, das in ein Unternehmen geht, ist das mehr wert als der Preis.
- Preise über `kie.py preis` prüfen; sie stehen bei Suno noch nicht in `preise.json` und
  müssten für belastbare Schätzungen nachgetragen werden.

## Phase 8c — Schnitt und Mischung (ffmpeg) — nur MODUS VIDEO

- Clips in Storyboard-Reihenfolge aneinanderhängen, vorher auf einheitliche fps und
  Auflösung normalisieren.
- Übergänge sparsam: harter Schnitt ist bei Erklärvideos fast immer richtig, eine kurze
  Blende nur bei Kapitelwechseln.
- Tonspuren mischen: Voiceover als Referenz, Musik leise darunter, Geräusche punktuell.
- Am Ende `-c:v libx264 -crf 23 -pix_fmt yuv420p -movflags +faststart`, damit die Datei auch
  im Browser sofort startet.

## Phase 8c2 — Kennzeichnung setzen (Pflicht, beide Modi)

Nach der Mischung, vor allem Weiteren:

```bash
python3 ~/.claude/skills/schulung/scripts/kennzeichnung.py <alle Master-Dateien>
```

Erst danach die Fassungen fürs Web ableiten, sonst fehlt der Hinweis dort.

**Wird eine bereits veröffentlichte Datei nachträglich gekennzeichnet, braucht ihre Adresse
eine neue Version** (`?v=2`). Der Browser hält Videodateien sonst hartnäckig im
Zwischenspeicher, und ausgerechnet der wiederkehrende Besucher sieht den Hinweis nie.

Gegenprobe, nicht überspringen: einen Frame bei 1,5 s und einen bei 3,5 s ziehen und ansehen.
Beim ersten muss der Hinweis stehen, beim zweiten weg sein.

## Phase 8d — Untertitel — nur MODUS VIDEO

**Gehören dazu, nicht als Kür.** In Unternehmen oft Pflicht, und sie kosten nichts, weil die
Wort-Zeitstempel aus Phase 4 schon vorliegen.

- **Niemals einbrennen.** Untertitel werden zugeschaltet, nicht aufgezwungen: als `.vtt`
  fürs Web und `.srt` daneben. Festgelegt von dem Auftraggeber am 20.08.2026, gilt für alle
  Produktionen. Der Grund ist nicht nur Geschmack — eingebrannter Text liegt über dem Bild,
  und wo das Bild der Inhalt ist (Whiteboard, Diagramme, Schrifttafeln), zerstört er genau
  das, was der Film zeigen will.
- Verlangt eine Plattform ausdrücklich eine eingebrannte Fassung, weil sie keine externen
  Spuren anzeigt, wird sie **zusätzlich** erzeugt und getrennt abgelegt. Die ausgelieferte
  Fassung ist immer die ohne.
- Zeilen auf höchstens 42 Zeichen und zwei Zeilen begrenzen, an Sinngrenzen umbrechen.
- **Zähl die Blocklängen nach, ffmpeg meldet dazu nichts.** Zwei stille Fehler am 17.08.2026:
  Ein Satz von 110 Zeichen rutschte ungeteilt durch, weil die Prüfung „zu langer Satz" hinter
  der Prüfung „passt noch in den Puffer" stand — die Reihenfolge muss umgekehrt sein. Danach
  blieb beim gierigen Auffüllen ein Rest von einem Wort übrig, der 0,4 s allein im Bild stand;
  richtig ist, einen langen Satz **gleichmäßig** auf die nötige Zahl Stücke zu verteilen und zu
  kurze Blöcke mit dem Nachbarn zu verschmelzen. Gegenprobe: längste Zeile, kürzeste Standzeit.
- Gegen den Sprechtext aus dem Storyboard prüfen, nicht gegen die Transkription: Die Stimme
  spricht, was im Skript steht, die Erkennung verschreibt sich bei Eigennamen.

## Phase 9 — Die HTML-Lerneinheit (eine Datei, Vanilla JS) — nur MODUS LERNEINHEIT

Template mit Platzhaltern (`{{VIDEO_V0}}`, `{{IMG_SZ1}}` …) bauen, am Ende per Python-Skript
alle Medien als Base64-Data-URIs einsetzen. Architektur:
- SPA mit Screens (`.screen.active`), Header mit Level-Badges + XP, Fortschrittsbalken
- Video-Screen wiederverwendbar (ein `<video>`-Element, src wird gewechselt; „Weiter"-Button
  pulsiert nach `ended`; Hinweis „Ton an!" in der Zielsprache)
- XP-Ökonomie: richtige Antwort volle Punkte, zweiter Versuch halbe, Level-Abschluss +25
- Level-Sperre: Interaktion muss abgeschlossen sein, Videos sind überspringbar
- Namenseingabe optional (nur zur Personalisierung des Feedbacks), nicht erzwingen
- Abschluss-Check: Fragen UND Antwortreihenfolge shuffeln, Auswertung mit Themen-Hinweisen
  zu falschen Antworten, Wiederholung möglich
- Zusammenfassungs-Screen: alle Merksätze aus dem Curriculum, XP-Stand, Print-CSS fürs Merkblatt
- localStorage: Fortschritt speichern, „Fortsetzen"-Button, Reset-Funktion
- `lang`-Attribut auf die Zielsprache setzen
- ⚠ In JS-Strings typografische Anführungszeichen der Zielsprache verwenden (deutsch „…“,
  englisch “…”), gerade `"` zerbrechen die Strings. **Das lähmt die ganze Seite, nicht nur die
  eine Zeile:** Der Syntaxfehler verhindert, dass irgendein Skript läuft. Die Seite sieht dabei
  normal aus und reagiert nur auf nichts. Gegenprobe mit `node --check` über den Skriptteil.

⚠ Drei Fallen, die am 16.08.2026 Zeit gekostet haben:

- **Große Videos als Data-URI laden in Chrome nicht.** Ein `<video>` mit 1,6 MB `data:`-Quelle
  bleibt bei `readyState 0` stehen, ohne Fehler. Base64 beim Laden einmal in ein `Blob`
  umwandeln und per `URL.createObjectURL` einhängen — die Datei bleibt trotzdem eigenständig.
- **Punkte des Abschluss-Checks ersetzen, nicht addieren.** Sonst hat, wer zweimal antritt,
  mehr XP als wer es gleich kann: `punkte(neu - (zustand.checkXp || 0))`.
- **Der Videobildschirm muss auf eine Fensterhöhe passen.** Nicht das Video begrenzen, sondern
  den Rahmen — dessen Höhe steht sofort fest, die Eigengröße des Videos erst nach dem Laden:
  `width: min(100%, calc((100vh - 300px) * 16 / 9))`.
- **`localStorage` ist bei `file://` oft gesperrt.** Beim Start einmal wirklich schreiben,
  lesen und löschen; bei Fehlschlag einen Satz einblenden, statt den Fortschritt still zu
  verwerfen. Wer die Datei über einen Server ausliefert, hat das Problem nicht.
- **Das Merkblatt beim Laden füllen, nicht erst am Ende.** Sonst druckt jeder, der mitten im
  Kurs `Cmd+P` drückt, eine leere Seite.

## Phase 10 — Browser-Test (Pflicht, vollständig) — nur MODUS LERNEINHEIT

Lokal serven (`python3 -m http.server`), dann komplett durchklicken:
1. Alle Videos dekodieren (Probe-Element je VIDEOS-Key, Dauer prüfen) + Bilder eingebettet
2. Jede Interaktion inkl. FEHLER-Pfaden (falsche Antworten, Timer ablaufen lassen)
3. Abschluss-Check absichtlich schlecht abschließen → Auswertung und Retry prüfen
4. Neu laden → „Fortsetzen" funktioniert; Konsole: null Fehler
5. Dateigröße prüfen; Server stoppen, Datei ausliefern

---

## Kosten

**Alle Zahlen kommen aus `kie.py`, keine aus dem Kopf und keine aus dieser Datei.** Was den
Preis treibt und womit man es rechnet:

| Posten | Rechnen mit |
|---|---|
| Videosekunden (dominiert alles andere) | `kie.py preis bytedance/seedance-1.5-pro --qualitaet 720p --sekunden N` |
| Bilder inkl. der zwei Referenzkandidaten | `kie.py preis gpt-image-2-text-to-image --qualitaet 2k --anzahl N` |
| Voiceover, gerechnet je 1.000 Zeichen | `kie.py preis elevenlabs --zeichen N` |
| Musik und Geräusche (Suno) | `kie.py preis` — noch nicht in `preise.json` hinterlegt |
| HyperFrames-Renders | kosten nichts, laufen lokal |
| Schnitt, Mischung, Untertitel | kosten nichts, laufen lokal |

Drei Hebel, in dieser Reihenfolge:

1. **Konzepte als HyperFrames-Animation lösen statt als Film.** Eine Animation kostet nichts
   und rendert schärferen Text. Nur was Emotion oder Menschen in Situationen zeigt, wird Film.
2. **Shot-Längen exakt auf die Voiceover zuschneiden.** Überschüssige Sekunden werden
   weggeschnitten und sind trotzdem bezahlt.
3. **Beim Standardmodell bleiben.** `seedance-2` kostet ein Vielfaches von
   `seedance-1.5-pro`, ohne für Schulungsmaterial sichtbar mehr zu liefern.

Nach dem Lauf gleicht `kie.py` die tatsächliche Abrechnung gegen die Vorhersage ab und warnt
ab 5 % Abweichung. Diese Warnung ernst nehmen, sie ist die verlässlichste Kostenkontrolle.

## Beispiel-Prompt (das gibt der User dir)

> /schulung — Erstelle ein Schulungsvideo zum Thema **[THEMA]**. Zielgruppe:
> **[z. B. neue Mitarbeitende / meine Coaching-Klienten / 10. Klasse]**, Sprache:
> **[z. B. Deutsch]**, Länge: **[z. B. ~3 Min]**, Stil: **[z. B. Graphic Novel]**.
> Inhalte sollen abdecken: **[Stichpunkte oder vorhandenes Material]**.
> Erstelle zuerst das Storyboard — erst nach meiner Freigabe produzieren.

Für die andere Ausgabeform stattdessen „als interaktive Lerneinheit in einer offline
lauffähigen HTML-Datei" und eine Dauer in Minuten Lernzeit angeben.

Fehlt eine der Kernangaben (Ausgabeform, Thema, Zielgruppe, Sprache, Dauer), nachfragen
statt raten. Nur bei der Ausgabeform ist die Annahme erlaubt: im Zweifel Video.
