---
type: skill
name: linkedin-carousel
agent_ready: true
schema_version: 1
bundle_skill: true
bundle_version: "0.28.0"
scope: vault
default_active:
  allgemein: false
  content: true
  business: false
  beratung: false
  automation: false
  image: false
conflicts_with: []
depends_on:
  - natur-sprech-check
trigger_words:
  - linkedin-carousel
  - carousel
  - karussell
  - wisch-carousel
  - wisch-karussell
  - swipe-deck
  - document-post
  - linkedin-pdf
  - 10-slide-deck
created: 2026-05-19
updated: 2026-05-19
description: "Baut aus einem fertigen LinkedIn-Post-Entwurf ein 10-Slide-Carousel als Document-Post-PDF (1080 × 1350, Warm-Navigator-Standard) und schreibt es in den Carousel-Unterordner des Posts. Folgt der eingefrorenen Build-Pipeline aus dem Designsystem-Template (Cover-Variante per Selection-Heuristik A/B/C/D, Slide-Katalog, Cover-A-Pre-Render bei Foto-Cover via Playwright, Natur-Sprech-Check als Pflicht-Gate, PDF-Export via Playwright). Wird aus linkedin-finalize aufgerufen oder direkt zur Zweitverwertung. ZWINGEND aufrufen, sobald der Anwender aus einem bestehenden Post oder Entwurf ein Carousel, Karussell, Wisch-Carousel, Swipe-Deck, 10-Slide-Deck, Document-Post oder LinkedIn-PDF bauen will, oder einen Post als Carousel zweitverwerten will. NICHT triggern für einzelnes Thumbnail (linkedin-finalize), Post-Text schreiben (linkedin-entwurf), Bild generieren (authentic-portrait, nano-banana-image, openai-image-gen), oder Präsentation für einen Vortrag (neue-praesentation, PowerPoint-Export)."
---

# LinkedIn Carousel

Du orchestrierst die Build-Pipeline. Die fachlichen Regeln zu Cover-Heuristik, Slide-Katalog und PDF-Export liegen im Designsystem-Template — dieser Skill ruft sie zur Laufzeit auf und übergibt Post-Text, Basisbild und Cover-Hint an die Pipeline. Keine Doppelung der Regeln in dieser SKILL.md, kein eigener Slide-Katalog hier.

Aufruf entweder aus `linkedin-finalize` (übergibt Post-Pfad und Basisbild als Args) oder direkt mit `/linkedin-carousel <post-titel>` zur Zweitverwertung.

## Inputs

Wenn der Skill aus `linkedin-finalize` gerufen wird, kommen die Args strukturiert mit:

- `Post-Datei` — absoluter Pfad zur Post-`.md` unter `{paths.posts_in_progress}` oder `{paths.posts_ready}`
- `Basisbild` — absoluter Pfad zu `<post>-thumbnail-base.png` neben der Post-`.md`
- `Cover-Hint` — `auto | A | B | C | D` (Default `auto`)

Bei direktem Aufruf (`/linkedin-carousel <post-titel>` oder ohne Argument):

1. Mit Argument — Glob über `{paths.posts_in_progress}/**/*.md` und `{paths.posts_ready}/**/*.md`, passende Post-Datei finden, Basisbild im selben Ordner suchen (`*-thumbnail-base.png`).
2. Ohne Argument — Liste aller Posts mit `status: bereit` oder `status: entwurf` anzeigen, Anwender entscheidet.

Wenn das Basisbild fehlt, Anwender kurz fragen und auf `linkedin-finalize` Schritt 4a-4d verweisen, dort wird das Basisbild erzeugt. Ohne Basisbild kann der Skill keine Cover-A-Variante bauen, fällt dann auf Cover B/C/D zurück (Type-only).

## Pflicht-Reads vor dem Build

Reihenfolge egal, aber alle gehören vor den ersten Slide-Entwurf. Parallel lesen, das spart 2-5 Sekunden gegenüber seriellem Lesen.

1. **`{paths.designsystem_carousel}`** — die eingefrorene Build-Quelle. Du arbeitest sie Section nach Section ab. Wenn ein Detail in dieser SKILL.md unklar ist, ist die Antwort dort.
2. **`{paths.designsystem_tokens}`** — Farben, Typo, Spacing verbatim. Keine Hex-Werte raten.
3. **`{paths.designsystem_globals_css}`** — Webseiten-Source-of-Truth für gold-dot, fg-Hierarchie, Headline-Weight, Letter-Spacing.
4. **`{paths.glossar}`** — Eigennamen verbatim. Anwender-Schreibweise im Chat ist nicht die Quelle.
5. **`{paths.branding}`** — Identität, falls Cover-Komposition oder Closing-Slide visuelle Entscheidungen verlangt.

## Foundation-Konsum: `_lib/designsystem/`

Dieser Skill konsumiert die Foundation-Library `_lib/designsystem/` indirekt über das Pflicht-Read-Template `{paths.designsystem_carousel}` und die `_lib/designsystem/Tokens/tokens.json`-Quelle. Das `_lib/designsystem/`-Modul liefert verbindliche Layout-Regeln, Token-Werte und Helper-Skripte für den Carousel-Build. Drift zwischen Vault-Template und Webseiten-`globals.css` immer zugunsten der Webseite auflösen.

## Helper-Skripte (im Bundle ausgeliefert)

Der Skill bringt drei Helper unter `scripts/` mit, die in den jeweiligen Carousel-Ordner kopiert werden:

- `export-carousel-pdf.py` — PDF-Build via Playwright + Headless-Chromium, `prefer_css_page_size=True`, `document.fonts.ready` als Gate.
- `bake-slide-01.py` — Cover-A-Pre-Render via Playwright, `device_scale_factor: 2.0`, erzeugt `slide-01.png` mit 2160 × 2700 Backed-Pixel.
- `preview.html` — HTML-Skeleton mit 10 Slide-Containern, `@media print`-Block und `@page { size: 1080px 1350px; margin: 0; }`, `page-break-after: always`.

## Cover-Heuristik

Selection-Heuristik aus dem Designsystem-Template, Section "Selection-Heuristik für KI-Agenten":

- `Cover A · Photo + Overlay` — Default mit Foto. Foto-Thumbnail vorhanden und Cover-Hint = `auto` oder `A`.
- `Cover B · Bold Type Dark` — provokante These als Hook.
- `Cover C · Frage Cream` — Frage oder Reflexion als Hook.
- `Cover D · Zahl-Hook` — drei- oder vierstellige Zahl als Hook.

`Cover-Hint: auto | A | B | C | D` (Default `auto`). Wenn Cover-Hint explizit gesetzt ist, diesen nehmen. Wenn `auto`, aus Post-Frontmatter und Hook-Charakter ableiten. Details und Entscheidungsbaum siehe `references/cover-heuristik.md`.

## Slide-Typen

Pro Story-Segment einen Slide-Typ aus der Selection-Tabelle nehmen:

| Story-Element | Slide-Typ |
|---|---|
| Kapitelwechsel | Section-Title |
| Konkrete Zahl | Big-Number (max. 2x pro Carousel) |
| Erzählender Absatz | Text-Body |
| Aufzählung 3-5 Punkte | Bullet-List |
| Zwei kontrastierende Werte | Comparison |
| Pointe / Autor-Gedanke | Quote · Deep Sea (genau 1x, etwa Mitte) |
| Bild stützt Text | Image + Caption |
| Drei rhythmische Sätze | Pull-Quote · Triade (direkt vor Closing) |

Dramaturgie folgt `Hook → Aufhänger → Mittelteil → Pointe → CTA`. Reihenfolge prüfen: höchstens zwei desselben Typs hintereinander, Quote und Triade nicht direkt nebeneinander, Closing immer letzte Slide.

## Build-Pipeline

### Schritt 1: Carousel-Ordner anlegen

Im Post-Ordner einen Unterordner `carousel/` anlegen. Helper-Skripte aus dem Bundle dorthin kopieren:

```bash
mkdir -p "<post-ordner>/carousel"
cp "{bundle_skill}/scripts/export-carousel-pdf.py" "<post-ordner>/carousel/"
cp "{bundle_skill}/scripts/bake-slide-01.py" "<post-ordner>/carousel/"
cp "{bundle_skill}/scripts/preview.html" "<post-ordner>/carousel/"
```

### Schritt 2: Bild-Assets sammeln

Alle benötigten Bilder lokal in den Carousel-Ordner kopieren. Keine `../../`-Pfade quer durch OneDrive — das hat im Live-Test unzuverlässig aufgelöst, lokale Kopien sparen die ganze Klasse von Pfad-Bugs.

- **Cover-Foto** (für Cover A) — `<post>-thumbnail-base.png` → `carousel/cover.png`
- **Avatar fuers Closing** — `{paths.avatar_default}` → `carousel/avatar.jpg`
- **Eventuelle BPMN-SVGs oder andere Schaubilder** — wenn der Post auf eine Prozessdoku verweist, das passende SVG aus dem Ressourcen-Bereich mitkopieren.

### Schritt 3: Cover-Variante festlegen

Cover-Heuristik (siehe oben) anwenden. Bei Cover B/C/D ist Schritt 9 (Cover-A-Pre-Render) nicht nötig — reine Type-Layouts.

### Schritt 4: Story segmentieren

Den Post-Text in 6 bis 10 narrative Segmente schneiden. Jedes Segment wird eine Slide. Dramaturgie folgt Hook → Aufhänger → Mittelteil → Pointe → CTA. Pro Segment Slide-Typ aus der Tabelle oben nehmen.

### Schritt 5: preview.html bauen

Ausgangspunkt ist die `preview.html` aus dem Bundle. Sie enthält bereits:

- `@media print`-Block mit `@page { size: 1080px 1350px; margin: 0; }`
- `.slide`-Klasse mit `page-break-after: always`
- 10 Slide-Container als Gerüst
- Bühnen- und Slide-Tag-Hide für den PDF-Export
- gold-dot-Klasse 1:1 aus `globals.css`
- Mona-Sans und Inter via Google Fonts

Slide-Inhalte austauschen anhand von Schritt 3 + 4. Tokens verbatim aus `tokens.json`. Headline-Weight 600 (Cover und Triade duerfen 700). Letter-Spacing in em (-0.035 / -0.028 / -0.025 / -0.02).

### Schritt 6: Iteration mit dem Anwender

`preview.html` im Browser laden. Anwender klickt durch alle Slides. Feedback einarbeiten, erneut anzeigen.

Sprache laientauglich gegen die Verbots-Liste prüfen:

**Verboten in Slide-Texten:** YAML, Vault, Obsidian, BPMN als Erklärbegriff, Single Source of Truth, Skill, Auditor, NC-Log, KVP-Log, Frontmatter, Markdown.

**Stattdessen:** Steckbrief, Schaubild, Begleiter, Prüfer, Logbuch, Mitdenker, Eigner, Tagesgeschäft, Steuerung.

KI ist Subjekt der Sätze, nicht der Anwender. Nicht "ich schreibe einen Steckbrief", sondern "ich rede mit der KI, sie zieht den Steckbrief nach".

### Schritt 7: Natur-Sprech-Check (Pflicht-Gate)

Vor dem PDF-Export Skill `natur-sprech-check` Modus B aufrufen, voller Slide-Text als Input. Drei Tells kommen besonders hartnäckig zurück und werden zwingend geprüft:

- Doppelpunkt mitten im Satz als Pointe-Trenner.
- Em-Dash oder Halbgeviert (— oder –) als Pointe-Trennzeichen.
- Komma vor und/oder.

Treffer ersetzen, dann erneut. Das ist ein hartes Gate — kein PDF-Build ohne durchlaufenen Natur-Sprech-Check.

### Schritt 8: Drift-Check gegen `globals.css`

Vor dem PDF-Export einmal Grep gegen die Verbots-Liste laufen lassen:

- Keine Tailwind-Slate-Neutrals
- Kein Manrope (alte Webseite)
- Kein reines `#FFFFFF` als Card-Background
- gold-dot als leerer `<span class="gold-dot"></span>` mit korrekten CSS-Properties (display inline-block, 0.22em, vertical-align baseline, margin-left 0.4em)

### Schritt 9: Cover-A-Pre-Render (nur bei Cover A · Photo + Overlay)

Bei Cover A flacht LinkedIn das Multi-Layer-Compositing falsch ab — dunkler Streifen quer über das Gesicht, unterer Verlauf zur Headline verschwindet. Lösung: Slide 1 vor dem PDF-Build als gebackene Bitmap ausrollen.

```bash
cd "<post-ordner>/carousel"
python bake-slide-01.py
```

Das Skript nutzt Playwright `page.locator("#slide-01").screenshot()` mit `device_scale_factor: 2.0` und erzeugt `slide-01.png` (2160 × 2700, ~4 MB). Anschließend zu JPEG `quality 92, optimize=True, progressive=True`:

```python
from PIL import Image
Image.open("slide-01.png").convert("RGB").save(
    "slide-01.jpg", "JPEG", quality=92, optimize=True, progressive=True
)
```

In `preview.html` die Slide-1-Section ersetzen durch:

```html
<section class="slide slide-01-baked" id="slide-01">
  <img src="slide-01.jpg" />
</section>
```

Plus minimales CSS:

```css
.slide-01-baked > img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
```

Bei `Cover B · Bold Type Dark`, `Cover C · Frage Cream` und `Cover D · Zahl-Hook` (reine Type-Layouts ohne Foto-Layer) ist dieser Schritt nicht nötig.

### Schritt 10: PDF rendern

```bash
cd "<post-ordner>/carousel"
python export-carousel-pdf.py
```

Das Skript awaitet `document.fonts.ready` (sonst rendert Chromium Times statt Mona Sans) und nutzt `prefer_css_page_size=True`. Output: `carousel.pdf` neben der HTML.

### Schritt 11: Output-Verifikation

- 10 Pages (Pflicht-Count)
- je 810 × 1013 pt (= 1080 × 1350 px @ 96 dpi)
- Dateigröße idealerweise unter 3 MB, **Hard-Limit 5 MB**

Wenn die PDF zu groß ist, Bilder vor dem Build auf 1080 px Breite skalieren und in sRGB konvertieren.

### Schritt 12: Titel-Vorschlaege für LinkedIn-Upload (ZWINGEND)

Nach dem PDF-Build und vor der Übergabe an `linkedin-finalize` immer **drei bis fuenf Titel-Vorschlaege** für den LinkedIn-Upload generieren. LinkedIn verlangt beim Document-Post einen Titel, der im Feed unter den Slides angezeigt wird und gleichzeitig als Dateiname beim Download dient — er ist eine zweite Hook-Chance neben dem Post-Text.

**Regeln:**

- Knapp halten (Richtwert 35-55 Zeichen, hartes Maximum 70).
- Unterschiedliche Hook-Patterns abdecken — Reframe-Hook, Frage, Plakativ-Statement, Triade. Nicht vier Variationen desselben Satzes.
- Nicht 1:1 die Cover-Headline kopieren — der Cover-Slide trägt die Headline schon, der PDF-Titel ist die zweite Hook-Ebene.
- Glossar-Pflicht — Eigennamen verbatim aus dem Glossar.
- Du-Adresse erlaubt und oft stärker als die Ich-Form.

**Ablage:** Vorschlaege im finalen Übergabe-Block an den Anwender anzeigen. Nach Auswahl den gewählten Titel im Pfad `{frontmatter.title_field}` des Post-Frontmatters speichern. Wenn das Feld noch nicht existiert, anlegen.

## State-Update nach erfolgreichem Build

1. **Frontmatter der Post-`.md`** aktualisieren:
   - `format: carousel` (statt `text+bild`)
   - `{frontmatter.title_field}: "<gewählter Titel>"` (aus Schritt 12)
   - `updated: <YYYY-MM-DD>` auf heute

2. **Carousel-Ordner-Inhalt** verifizieren, dass folgende Dateien drin liegen:
   - `preview.html`
   - `carousel.pdf`
   - `export-carousel-pdf.py`, `bake-slide-01.py` (bei Cover A)
   - `cover.png`, `avatar.jpg`, ggf. weitere Assets
   - `slide-01.png`, `slide-01.jpg` (bei Cover A)

3. **Bei Aufruf aus `linkedin-finalize`** — Kontrolle zurückgeben. `linkedin-finalize` läuft mit den Folge-Schritten (Hashtags, Ordner-Umzug nach `{paths.posts_ready}`) weiter. Der Carousel-Ordner zieht beim Eltern-Ordner-Move automatisch mit.

4. **Bei direktem Aufruf** — Anwender informieren, wo das Carousel liegt, und kurz erinnern: für den Upload bei LinkedIn das `carousel.pdf` als "Dokument hinzufügen" auswählen, nicht als Bild. Den gewählten PDF-Titel aus Schritt 12 nennen — er ist der Pflicht-Titel beim Upload.

## Hinweise zum Verhalten

- **Dynamisches Regelwerk** — Standards, Slide-Katalog, Selection-Heuristik und Selection-Tabelle leben im Designsystem-Template, nicht in dieser SKILL.md. Wenn der Anwender die Standards modifiziert, modifiziert er das Template, und der Skill übernimmt das automatisch.
- **Glossar-Pflicht, Updated-Pflicht, Branding-Pflicht** aus den globalen Konventionen gelten.
- **Fonts beim PDF-Export** sind ein häufiger Fail-Point. Wenn das PDF Times statt Mona Sans zeigt, ist `document.fonts.ready` nicht abgewartet worden — Helper-Skript prüfen.
- **Cover-A-Pre-Render** ist Pflicht bei Foto-Cover. Wer den Schritt überspringt und LinkedIn dann das PDF rendert, sieht den dunklen Streifen über dem Gesicht.
- **Iteration kostet nichts** — Helper-Skripte sind schnell, beliebig viele Runden möglich, ohne dass eine Bild-KI bemüht werden muss.

## Konfiguration

Pfad-Platzhalter und optionale Frontmatter-Felder werden über `skill.config.yaml` aufgelöst (Beispiel: `skill.config.example.yaml`). Pflicht-Felder unter `paths`:

- `paths.posts_in_progress`
- `paths.posts_ready`
- `paths.designsystem_carousel`
- `paths.designsystem_tokens`
- `paths.designsystem_globals_css`
- `paths.avatar_default`
- `paths.glossar`
- `paths.branding`

Optionales `frontmatter.title_field` (Default: `linkedin_title`) steuert, in welches Frontmatter-Feld der in Schritt 12 gewählte PDF-Titel geschrieben wird.

## Referenzen

- `references/cover-heuristik.md` — Cover-Algorithmus und Entscheidungsbaum für A/B/C/D.
- `eval/test-trigger-mit-foto-cover.eval.json` — End-to-End mit Foto-Cover (aus `linkedin-finalize` aufgerufen).
- `eval/test-direktaufruf-cover-c-frage.eval.json` — Direktaufruf, Cover C, kein Basisbild.
- `eval/test-fehlendes-basisbild.eval.json` — Fehlendes Basisbild bei Cover-A-Default.
- `eval/test-cover-heuristik.eval.json` — Vier-Cover-Varianten-Smoke-Test.
