# NODE — Network Of Document Elements

**NODE** ist das **erweiterte Markdown-Subset** für die **Projekt-Bodies** in diesem Portfolio: strukturierte Blöcke (Galerie, Medien, Buttons, Layout, Metadaten-Zeilen) plus ein klar definierter **Baseline** für Fließtext. Der Name betont die **vernetzten Bausteine** (Blöcke + Inline), aus denen der gerenderte Inhalt entsteht.

| Aspekt | Details |
|--------|---------|
| **Einsatz** | `projects/<slug>.md` — Inhalt **unterhalb** des YAML-Frontmatters (`---`). |
| **Rendering** | `parseMarkdown()` in `app.js` → HTML im Projekt-Modal (`.md-content`). |
| **Kein vollständiges CommonMark** | Absichtlich reduziertes Set: Überschriften, Absätze, einfache Listen, Zitate, Inline-Formatierung — siehe [Baseline](#baseline-fließtext--inline). |

---

## Verarbeitungspipeline

Alle **NODE-Blöcke** werden **vor** dem Zeilen-/Absatz-Parsing eingespeist (Reihenfolge relevant für verschachtelte Inhalte in `[split]`):

1. `preprocessGalleryBlocks` — `[gallery]`
2. `preprocessMediaBlocks` — `[media]`
3. `preprocessButtonsBlocks` — `[buttons]`
4. `preprocessSplitBlocks` — `[split]` (ruft für Spalten intern erneut `parseMarkdown` auf)
5. `preprocessStackBlocks` — `[stack]`

Anschließend: Überschriften, horizontale Linie, Listenzeilen, Blockzitate, dann **Aufteilung in Absätze** (`\n\n+`), Block-Erkennung (u. a. durchgereichte NODE-HTML-Fragmente), Fließtext als `<p>` mit `parseInlineRich`.

---

## Projekte laden (Slug-Liste)

| Thema | Verhalten |
|-------|-----------|
| **Datei** | `projects/<slug>.md`; Slug = Dateiname ohne `.md`. |
| **Index** | Keine manuelle Liste im Code. **Lokal** (z. B. `python -m http.server`): Slugs aus der **Verzeichnis-HTML** von `projects/` (`*.md`, ohne `README.md`). **Statisches Hosting** (z. B. GitHub Pages): Fallback **`projects/projects-index.json`**. |
| **Generieren** | Repo-Wurzel: `node scripts/generate-projects-index.mjs` — liest alle `projects/*.md` außer `README.md`, schreibt die JSON-Datei. Nach neuen `.md`-Dateien bzw. vor Deploy ausführen und `projects-index.json` versionieren. |
| **Sortierung Karten** | Unabhängig von der JSON-Reihenfolge: **Jahr absteigend**, bei gleichem Jahr **Titel A–Z** (`sortProjectsLoaded` in `app.js`). |

### Archiv-Entscheid

- Inhalte unter `projects/archive/` oder `projects/outdated/` gelten als **archiviert**.
- Sie werden bewusst **nicht** in `projects/projects-index.json` aufgenommen.
- So bleiben alte/abgelöste Projekte versioniert, ohne in der normalen Portfolio-Liste zu erscheinen.

### Qualitätssicherung

- `node scripts/validate-content.mjs` prüft Frontmatter-Basics und offensichtliche Inhaltsfehler.
- Empfohlen vor Deploy: zuerst Index generieren, dann validieren.

---

## Startseite (Tags)

Auf der Startseite erscheinen die **drei häufigsten** Tags als Chips; weitere Tags unter **„Mehr“**. Konstante **`FILTER_TOP_TAGS`** in `app.js` (aktuell `3`).

---

## Baseline (Fließtext & Inline)

Diese Konstrukte gelten **überall** im Body — auch innerhalb von `[left]` / `[right]` in `[split]`.

| Feature | Syntax / Hinweis |
|---------|------------------|
| **Überschriften** | `#`, `##`, `###` — jeweils eine Zeile. |
| **Trennlinie** | Zeile nur `---`. |
| **Listen** | `- Punkt` oder `1. Punkt` — werden als **`<ul>`** mit `<li>` gerendert (nummerierte Eingabe optisch nicht als `<ol>` ausgezeichnet). |
| **Zitat** | `> Zeile` — zeilenweise; mehrzeilige Zitate ohne Leerzeilen dazwischen hängen im selben Block zusammen. |
| **Absätze** | Durch **Leerzeile** getrennt; innerhalb eines Absatzes: Zeilenumbrüche → `<br />`. |
| **Einzelbild (groß)** | Absatz, der **ausschließlich** eine Bildzeile `![Alt](url)` enthält → `<figure class="md-figure">`. Bild + Text im selben Absatz → Bild inline im `<p>`. |
| **Links** | `[Text](url)` — im Linktext: `` `Code` ``, `**Fett**`, `*Kursiv*`. Erlaubte Ziele: `http(s)`, Pfade ab `/`, `mailto:`, `tel:` (`safeUrl`). |
| **Inline-Bild** | `![Alt](url)` im Fließtext. |
| **Tabellen** | [GFM-ähnlich](https://github.github.com/gfm/): Kopfzeile, Trennzeile mit `---` / `:---` / `---:` / `:---:`, Datenzeilen — alles mit `\|` getrennt. **Keine Leerzeile** innerhalb einer Tabelle (sonst zerlegt der Parser den Block). Ausgabe: `md-table-wrap` / `md-table`. |

---

## Block: `[gallery]`

| | |
|---|---|
| **Zweck** | Bis zu **zwei** Bilder **nebeneinander** (4:3); mehr Bilder werden in **Paaren** mit optional einzelnem Restzeilen ausgegeben. |
| **Syntax** | Pro Zeile genau `![alt](url)`; Leerzeilen ignoriert; andere Textzeilen ignoriert. |
| **HTML** | u. a. `md-gallery`, `md-figure`. |

```markdown
[gallery]
![Alt 1](https://…)
![Alt 2](https://…)
[/gallery]
```

---

## Block: `[media]`

| | |
|---|---|
| **Zweck** | Embeds (Video, Karten, Figma, Spotify, …) — **keine** reinen Bilder (dafür Einzelbild / `[gallery]`). |
| **Syntax** | Pro Zeile eine URL; Leerzeilen ignoriert. |
| **URLs** | Einbindung bevorzugt **https**; fehlendes Schema / `http` wird in der Embed-Logik normalisiert, wo vorgesehen. **YouTube:** In der URL muss eine **echte Video-ID** stehen (typisch **11 Zeichen**). Aus Dokumentationen kopierte Platzhalter — z. B. `watch?v=…` bzw. `watch?v=%E2%80%A6` (Unicode-Auslassung `…`) — sind **keine** gültige ID und führen **nicht** zu einem Embed. Korrekt sind vollständige Links wie `https://www.youtube.com/embed/bzTntRHmpa8` oder `https://www.youtube.com/watch?v=bzTntRHmpa8` (Video-ID aus „Teilen“ übernehmen). |
| **Fallback** | Unbekannte URLs → Link-Karte statt iframe; Pfade mit `/embed/` → generisches 16:9-iframe (Heuristik). |

```markdown
[media]
https://www.youtube.com/embed/bzTntRHmpa8
https://www.figma.com/file/…
[/media]
```

**Erkannte Dienste (Auswahl):** u. a. YouTube, Vimeo, Figma, Spotify (Track/Album/Playlist/Episode/Show), CodePen, X/Twitter (Status), Instagram (Post/Reel), Google Maps (nur URLs mit `/maps/embed`), Loom, Dailymotion, SoundCloud, Miro (Board/Embed).

---

## Block: `[buttons]`

| | |
|---|---|
| **Zweck** | Eine oder mehrere **Call-to-Action**-Buttons (nicht im Fließtext versteckt). |
| **Syntax** | Pro Zeile `{btn:…}` mit `\|`-getrennten Segmenten und optional `key=value`. |
| **Parameter** | `url`, `type` (`page` \| `external` \| `video` \| `download`), `icon` (`none` \| `left` \| `right` \| `up` \| `down`), `variant` (`primary` \| `secondary` \| `ghost`). Ohne gültige URL → Button inaktiv. |

```markdown
[buttons]
{btn:Download|url=https://example.com/a.zip|type=download|icon=right|variant=primary}
{btn:Live Demo|url=https://example.com|type=external|icon=right|variant=secondary}
[/buttons]
```

---

## Block: `[split]`

| | |
|---|---|
| **Zweck** | Zwei inhaltlich zusammengehörige Spalten (Text+Bild, Vorher/Nachher, Text+Embed …). |
| **Layout** | Desktop: zwei Spalten; schmale Viewports (≤640px): **gestapelt**, Reihenfolge links → rechts. |
| **Verschachtelung** | **`[split]` innerhalb einer Spalte** wird vom Parser **nicht** unterstützt (Regex-Grenze). |

```markdown
[split]
[left]
**Vorher:** …
[/left]
[right]
**Nachher:** …
[/right]
[/split]
```

In den Spalten gilt der vollständige NODE-Baseline inkl. der anderen Blöcke (`[gallery]`, `[media]`, `[buttons]`, `[stack]`).

---

## Block: `[stack]`

| | |
|---|---|
| **Zweck** | Kompakte **Key-Value-Zeilen** (Rolle, Tools, Jahr, …). |
| **Syntax** | Pro Zeile `Label: Wert` — **erstes** `:` trennt Label und Wert (Label ohne `:`). |
| **Wert** | Inline-Markdown (`parseInlineRich`); Label nur escapter Text. |
| **HTML** | `<dl class="md-stack">` mit `<dt>` / `<dd>` pro Zeile (in `div.md-stack__row`). |

```markdown
[stack]
Role: UI/UX Design
Tools: Figma, Webflow
Year: 2025
Platform: Web
[/stack]
```

---

## Konventionen & Grenzen

- **Block-Tags** in der Praxis **klein schreiben** (`[gallery]`, …); Parser nutzen meist case-insensitive Muster.
- **Kein beliebiges HTML** im Markdown-Body — Ausnahme sind die von NODE erzeugten, in `app.js` gehärteten HTML-Schnipsel.
- **Komplexität:** NODE ist für **Portfolio-Projektseiten** optimiert, nicht für lange Dokumentation mit verschachtelten Listen oder Tabellen.

---

## Referenz

| Ressource | Ort |
|-----------|-----|
| Parser & Pipeline | `app.js` — `parseMarkdown`, `preprocess*` |
| Projektliste generieren | `scripts/generate-projects-index.mjs` |
| Darstellung Modal | `style.css` — `.md-content`, `.md-gallery`, `.md-media`, `.md-buttons`, `.md-split`, `.md-stack` |
