# CLAUDE.md
-Diese Datei enthält Anweisungen für Claude Code (claude.ai/code) beim Arbeiten mit diesem Verzeichnis.
+Diese Datei enthält Anweisungen für Claude Code (claude.ai/code) beim Arbeiten mit diesem Repository.
## Arbeitsregeln
-- Wenn du nicht explizit anders angewiesen wirst, arbeitest du nur innerhalb dieses Repositories und des Theme-Submodules.
- Du arbeitest immer auf dem bereits ausgecheckten Branch `content--via-astro`.
- Ab und zu bin ich ungenau oder bringe Details durcheinander. Wenn du den Eindruck hast, dass etwas nicht passt oder Details fehlen, frage immer direkt nach.
- Für alle Aufgaben gilt: du kannst und sollst Nachfragen stellen, bevor du mit der Umsetzung beginnst, wenn das sinnvoll ist.
- Erstelle nach dem Abschluss einer Aufgabe jeweils einen Commit — direkt, ohne vorher nachzufragen.
- Fasse in der Commit-Nachricht zusammen, was und warum geändert wurde.
-- Wenn du zugehörige Änderungen im Theme-Repository gemacht hast, erstelle auch dort einen separaten Commit mit einer ebenfalls sinnvollen Beschreibung.
-- Wenn du meinst, dass ein Theme-Commit besonders wichtig oder unabhängig verständlich ist, weise mich darauf hin.
-
-### Submodule-Pointer-Updates
-
-Der Zeiger auf den Theme-Commit (`themes/hugo-juplo`) darf **nicht automatisch** mit in Commits des Haupt-Repos einfließen. Er soll nur aktualisiert werden, wenn eine inhaltlich zusammengehörige Änderung im Haupt-Repo dies erfordert — und selbst dann nur nach Rückfrage. Begründung: Ein Submodule-Pointer-Update koppelt implizit einen bestimmten Theme-Stand an den Content und sollte eine bewusste, begründete Entscheidung sein, kein Nebenprodukt.
## Projektstruktur
-Das Projekt besteht aus zwei Git-Repositories:
+Das Projekt ist eine statisch generierte Website, gebaut mit **Astro 6.4.4**. Wichtigste Verzeichnisse:
```
-content--via-hugo (dieses Repo)
-├── content/ ← Blog-Artikel, Projects-Doku
-│ ├── _index.md ← Startseite
-│ ├── blog/ ← Blog-Artikel, nach Jahr organisiert unter archive/
-│ └── projects/ ← Maven-Plugin-Dokumentation (generiert, versioniert)
-├── hugo.yaml ← Hugo-Konfiguration
-├── data/ ← YAML-Datendateien (comments, library)
-├── static/ ← Statische Assets (Maven-Site-Outputs etc.)
-└── themes/hugo-juplo/ ← Git-Submodule (Branch: frontend)
- ├── layouts/ ← Hugo-Templates
- ├── assets/scss/ ← SCSS-Quellen (5 Breakpoints + Print)
- ├── content/ ← Struktureller Site-Content (About, Impressum, Taxonomien)
- └── exampleSite/ ← Standalone-Testsite für Theme-Entwicklung (hugo.dev/hugo-themes best practice)
+src/
+ pages/ ← Dateibasiertes Routing (alle Seiten und API-Routen)
+ content/
+ blog/ ← Blog-Artikel als Markdown (Content Collection, glob-Loader)
+ projects/ ← Projektdokumentation als HTML + YAML-Frontmatter (Custom Loader)
+ components/ ← Wiederverwendbare Astro-Komponenten (BlogNav.astro, ProjectPage.astro, …)
+ lib/ ← Shared TypeScript-Helpers (posts.ts, projects.ts)
+ styles/ ← SCSS-Quellen
+public/
+ css/ ← Compiliertes CSS (aus SCSS, im Git eingecheckt)
+ img/ ← Bilder und statische Assets
+ projects/ ← Generierter Maven-Site-Output (apidocs, xref etc.) — ohne Astro-Layout
+data/ ← YAML-Datendateien (Kommentare, Bibliothek)
```
-Das Theme ist als Git-Submodule unter `themes/hugo-juplo` eingebunden (Branch `frontend` im selben Git-Server-Repo unter einem anderen Branch). Änderungen am Theme werden im Submodule-Verzeichnis committet, Änderungen am Content im Hauptrepo – immer getrennt.
-
-Zur CLAUDE.md im Theme-Submodule: `themes/hugo-juplo/CLAUDE.md`
-
-## Template-Struktur
+## HTML-Seitenstruktur
-`baseof.html` definiert das HTML-Gerüst aller Seiten:
+`BaseLayout.astro` definiert das HTML-Gerüst aller Seiten über benannte Slots:
```
body
└── #page
├── header#header Logo + Slogan
- ├── #breadcrumb (block: breadcrumb – überschreibbar)
+ ├── #breadcrumb (slot: breadcrumb)
└── main.content
- ├── article#content (block: article → block: title + block: content)
+ ├── article#content (slot: article)
└── .marginal
- ├── nav#nav (block: menu – Menü/Sitemap)
- └── asides (block: marginalcontent – Taxonomien, Custom-Content)
+ ├── nav#nav (slot: menu — Sitemap-Menü, s.u.)
+ └── asides (slot: marginalcontent — Taxonomien, Custom-Content)
└── footer#footer Copyright + Footer-Links
```
-Spezialisierte Layouts nach Seitentyp (alle als `{{ define "block" }}` Overrides):
-
-| Layout | Verwendung | Besonderheit |
-|---|---|---|
-| `index.html` | Startseite | überschreibt `article` |
-| `section.html` | Generic-Section | überschreibt `content` |
-| `article.html` | Generic-Artikel | überschreibt `article` |
-| `page.html` | Standalone-Seite | überschreibt `content` + `marginalcontent` (zeigt Taxonomie-Terms) |
-| `blog/section.html` | `/blog/` | überschreibt `menu` + `article` |
-| `blog/archive/section.html` | `/blog/archive/` | überschreibt `menu` + `content` + `title` |
-| `blog/archive/year.html` | `/blog/archive/YYYY/` | überschreibt `menu` + `content` + `title` |
-| `blog/archive/page.html` | Blog-Artikel | überschreibt `menu` + `content` |
-| `categories/taxonomy.html` | `/categories/` | überschreibt `breadcrumb` + `menu` + `content` |
-| `categories/term.html` | `/categories/X/` | überschreibt `breadcrumb` + `menu` + `content` |
-| `tags/taxonomy.html` | `/tags/` | überschreibt `breadcrumb` + `menu` + `content` |
-| `tags/term.html` | `/tags/X/` | überschreibt `breadcrumb` + `menu` + `content` |
-
-## Menü-Konzept (SEO-Sitemap)
-
-Das Menü unter `nav#nav` ist bewusst am **Ende des HTML** platziert (im `.marginal`-Bereich) und dient gleichzeitig als SEO-Sitemap. Es hat zwei Ebenen:
-
-**Ebene 1 – Section-Menu (`#menu`):** Horizontale Hauptnavigation aus `MainSections` (Blog, Projects, About).
-
-**Ebene 2 – Sub-Menu (`#submenu`):** Baumstruktur der gesamten Site ab Home. Zeigt immer den vollständigen Baum, wobei:
-- Items mit Kindern erhalten Klasse `sub`
-- Items außerhalb des aktuellen Pfads erhalten Klasse `off`
-- Der aktive Link und seine Vorfahren erhalten `class="selected"` am `<a>`-Element
-
-Das rekursive Partial `menu/tree.html` handhabt die Projects-Sektion mit spezieller Sichtbarkeitslogik: Von Projektversionen werden im Menü nur Seiten angezeigt, die sich im aktuellen URL-Pfad befinden oder im Frontmatter `current: true` gesetzt haben.
-
-Für den Blog-Ast (Archive, Categories, Tags) gibt es separate Template-Logik in den jeweiligen Layout-Dateien, da Taxonomy-Seiten nicht im normalen Hugo-Seitenbaum liegen.
-
-## Content-Architektur (Zielzustand)
-
-- **Theme** (`themes/hugo-juplo`): ausschließlich Layouts, SCSS, Partials, Archetypes, `exampleSite`
-- **Dieses Repo**: ausschließlich Content (alle `.md`/`.html` Seiten inkl. About, Impressum)
-
-Der `exampleSite`-Ansatz im Theme dient zum isolierten Testen des Themes unabhängig vom Content-Branch.
-
-## Stand der Umsetzung
-
-Das Theme ist funktional (Menü, Breadcrumb, Taxonomien, Blog-Archiv, Syntax-Highlighting, responsive Layout). Die Template-Implementierung enthält noch Copy-paste-Duplikate, die schrittweise konsolidiert werden sollen. Größte offene Aufgabe: Den `menu`-Block aus den 7+ Einzel-Templates in ein parametrisiertes Partial konsolidieren.
-
-Bekannte Baustellen und ihre Priorität sind in der Analyse vom 2026-06-03 dokumentiert.
+Seiten befüllen die Slots mit `<Fragment slot="…">`. Den `menu`-Slot übernimmt immer `BlogNav.astro`.
---
-## Astro-Implementierung: Design- und Architektur-Entscheidungen
-
-Dieser Abschnitt dokumentiert die technischen Entscheidungen des Astro-Branches (`content--via-astro`). Er unterscheidet zwischen Entscheidungen, die Astro-Standard folgen, und solchen, bei denen bewusste Abweichungen getroffen wurden.
-
-### Standard-Entscheidungen (Astro Best Practices)
-
-**Statische Site-Generierung (SSG)**
-Astro erzeugt beim Build statische HTML-Dateien. Der `output`-Mode ist nicht explizit konfiguriert, da `'static'` Astros Default ist.
-Quelle: https://docs.astro.build/en/basics/rendering-modes/
-
-**Content Collections mit glob-Loader**
-Blog-Artikel in `src/content/blog/` werden über Astros Content Collections API eingebunden. Die Konfiguration in `src/content.config.ts` nutzt den `glob()`-Loader, der alle `.md`-Dateien im Verzeichnis einliest und Schema-Validierung (Zod) für Frontmatter-Felder ermöglicht.
-Quelle: https://docs.astro.build/en/guides/content-collections/
-
-**Dateibasiertes Routing**
-Jede Datei in `src/pages/` ergibt eine Route. Dynamische Routen (`[year].astro`, `[term].astro`) implementieren `getStaticPaths()`, um alle Pfade zur Build-Zeit zu erzeugen.
-Quelle: https://docs.astro.build/en/guides/routing/
+## Sitemap-Menü
-**Named Slots in Layouts**
-`BaseLayout.astro` definiert benannte Slots (`breadcrumb`, `article`, `menu`, `marginalcontent`, `footerlinks`), die von Seiten mit `<Fragment slot="…">` befüllt werden. Dies spiegelt direkt Hugo's `{{ define "block" }}`-Pattern.
-Quelle: https://docs.astro.build/en/basics/layouts/
+Das Menü unter `nav#nav` ist bewusst am **Ende des HTML** platziert und dient primär als **SEO-Sitemap**: Es enthält den vollständigen Seitenbaum als verlinkten Baum, damit Suchmaschinen-Robots alle Seiten entdecken und crawlen können. Im Browser dient es gleichzeitig als sekundäre Navigation.
-**TypeScript im Frontmatter**
-Alle `.astro`-Dateien nutzen TypeScript im `---`-Frontmatter. Astro kompiliert das automatisch, ohne separate Build-Konfiguration.
-Quelle: https://docs.astro.build/en/guides/typescript/
+### Aufbau
-**`public/` als Verzeichnis für statische Assets**
-Statische Dateien (CSS, JS, Bilder, Fonts) liegen in `public/` und werden 1:1 ins Build-Verzeichnis kopiert. Hugo nannte dieses Verzeichnis `static/` — in Astro ist `public/` der Standard. Die Dateien wurden bei der Migration entsprechend verschoben.
-Quelle: https://docs.astro.build/en/basics/project-structure/#public
+**Ebene 1 — Section-Menu (`#menu`):** Horizontale Hauptnavigation mit drei Hauptbereichen: Blog, Projects, About.
-**RSS-Feeds via `@astrojs/rss`**
-RSS-Feeds werden mit dem offiziellen Astro-Integration-Paket `@astrojs/rss` erzeugt. Jede Feed-Datei ist eine TypeScript-API-Route (`.ts`-Endung statt `.astro`), die eine `GET`-Funktion exportiert.
-Quelle: https://docs.astro.build/en/guides/rss/
+**Ebene 2 — Sub-Menu (`#submenu`):** Der vollständige Seitenbaum als verschachtelte Listenstruktur. Alle Items sind **immer im HTML enthalten** — CSS blendet nicht relevante Äste aus:
----
-
-### Eigene Entscheidungen (nicht Astro-vorgegeben)
+- Items mit Kindern erhalten Klasse `sub`
+- Items außerhalb des relevanten Pfads erhalten Klasse `off` → `display: none` (Classic-Layout: `#submenu li.s.off { display: none }`)
+- Der aktive Link und seine Vorfahren erhalten `class="selected"` am `<a>`-Element
+- Aktive `<ul>`-Ebenen erhalten Klasse `active`
-#### CSS: Separater SCSS-Build statt Astro-Integration
+Ein `ul.s` ohne `active` blendet sich **nicht selbst** aus — nur `li.s.off` ist hidden.
-**Was:** SCSS wird über einen separaten `sass`-CLI-Aufruf kompiliert (`npm run build:css`), nicht über Astros eingebaute SCSS/Vite-Integration. Das kompilierte CSS landet in `public/css/` und ist im Git eingecheckt.
+### Implementierung: `BlogNav.astro`
-**Warum:** Das SCSS stammt ursprünglich aus dem Hugo-Theme-Repository (`themes/hugo-juplo`), das mit der Astro-Migration entfernt wurde. Die SCSS-Quellen liegen jetzt in `src/styles/` und sind authoritative Kopie im Website-Repo. Eine Integration in Astros Asset-Pipeline (Vite/PostCSS) war möglich, wurde aber nicht gewählt, um das bewährte SCSS-Build-Setup unverändert zu übernehmen. Der separate Build-Schritt (`sass src/styles/screen.scss public/css/screen.css` etc.) ist einfacher als eine Vite-Plugin-Konfiguration. Der `prebuild`/`predev`-Hook in `package.json` stellt sicher, dass CSS immer aktuell ist.
+`BlogNav.astro` empfängt `currentUrl` als Parameter und berechnet daraus vollständig in TypeScript alle Navigationszustände (`onBlog`, `onArchive`, `onCategoriesIndex`, `onProjectsSection` etc.). Alle CSS-Klassen werden direkt aus diesen Variablen abgeleitet. Nie werden Items per `{condition && <li>}` weggelassen — nur die `off`-Klasse steuert die Sichtbarkeit via CSS.
-**Konsequenz:** SCSS-Änderungen erfordern `npm run build:css` oder einen Dev-Server-Neustart. HMR funktioniert für SCSS nicht automatisch.
+### Section-`off`-Logik
----
+Welche der drei Hauptsections sichtbar ist, hängt vom aktiven Bereich ab:
-#### Taxonomy-URL-Struktur: Split zwischen Index und Term
+- `blogSectionOff = onAboutSection || onProjectsSection`
+- `projectsSectionOff = blogSelected || onAboutSection`
+- `aboutOff = blogSelected || onProjectsSection`
+- Auf der Homepage und Root-Standalone-Seiten (keiner Section zugehörig) sind alle drei Sections sichtbar
-**Was:** Die Taxonomie-Seiten sind auf zwei URL-Ebenen verteilt:
-- Index-Seiten: `/blog/categories/` und `/blog/tags/` (unter dem Blog-Pfad)
-- Term-Seiten: `/categories/TERM/` und `/tags/TERM/` (direkt unter Root)
+### Item-`off`-Logik innerhalb einer Section
-**Warum:** Diese Struktur ist identisch mit dem Hugo-Output. Hugo legt Taxonomie-Listenseiten (`/blog/categories/`) unter dem konfigurierten `taxonomyTerms`-Pfad ab, während Term-Seiten (`/categories/kafka/`) direkt unter dem Taxonomie-Namen generiert werden. Die Beibehaltung dieser Struktur ist notwendig für URL-Kompatibilität mit bestehenden Links und SEO.
+Innerhalb einer Section gilt:
+```
+off = !(isCurrent || isAncestor || isSibling || isChild)
+```
+Dabei ist `isSibling` nur aktiv, wenn die aktuelle Seite kein Section-Index-Node ist (`!isNode`). Section-Index-Seiten unterdrücken also die Geschwister-Sichtbarkeit und zeigen nur ihre eigenen Kinder.
---
-#### BlogNav: Explizite State-Machine statt Template-Rekursion
-
-**Was:** `BlogNav.astro` berechnet den Navigationszustand vollständig in TypeScript-Variablen (`onBlog`, `onArchive`, `onCategoriesIndex`, `onCategoryTerm`, etc.) und rendert das HTML anhand dieser Flags.
+### Blog-Bereich: Sonderregeln
-**Warum:** Hugo's `menu/tree.html` ist ein rekursives Partial, das den Baum traversiert und dabei Hugo-interne Konzepte wie `.IsDescendant`, `.Ancestors`, `.Eq` nutzt. Diese existieren in Astro nicht. Statt einer allgemeinen Lösung wurde eine explizite State-Machine gewählt, die die konkreten Seitentypen kennt und die CSS-Klassen (`off`, `sub`, `selected`, `active`) direkt ableitet.
+**Situation:** Blog-Artikel haben kanonische SEO-URLs, die nicht immer dem Dateibaum entsprechen, und tauchen in mehreren Kontexten auf: chronologisch im Archiv, und unter jeder ihrer Categories bzw. Tags.
-**Wichtige Regel:** `#submenu li.s.off { display: none }` (Classic-Layout). Ein `ul.s` ohne `active` blendet sich nicht selbst aus — nur `li.s.nav-leaf` und `li.s.off` sind hidden.
+**Anforderung:** Jeder Artikel soll im Sitemap-Menü **genau einmal** erscheinen — unter seiner kanonischen URL im Blog-Archiv. Die Taxonomy-Bereiche (Categories, Tags) zeigen nur die Term-Übersichten, keine doppelten Artikel-Verlinkungen.
-**`off`-Klassen-Logik** (aus Hugo-Template-Quellen im `frontend`-Branch hergeleitet):
+**Umsetzung im Nav:**
+- Die Jahresstruktur (`/blog/archive/YYYY/`) mit Posts darunter ist immer im HTML; die `off`-Klasse steuert die Sichtbarkeit:
+ - Jahreszahlen: `off` wenn weder der Archive-Bereich noch das jeweilige Jahr aktiv ist
+ - Post-Items: immer gerendert; sichtbar sobald das Eltern-Jahr sichtbar ist
+- Auf Taxonomy-Seiten (Categories-Index, Tags-Index, Term-Seiten) ist der Blog-Archiv-Ast im Nav hidden, aber die Blog-Section bleibt als Hauptbereich sichtbar
-Hugo verwendet zwei verschiedene Menü-Templates:
-- `layouts/_partials/menu/blog.html` — für Blog-Seiten: Blog immer sichtbar, alle anderen Sections immer `off`
-- `layouts/_partials/menu/default.html` + `tree.html` — für alle anderen Seiten: Section-Index-Seiten (`IsNode=true`) blenden alle anderen Sections aus; auf der Homepage und regulären Root-Seiten (Impressum, Datenschutz) sind alle Sections sichtbar (Kind- bzw. Geschwister-Logik in `tree.html`)
+### About-Bereich: Seitenstruktur
-Daraus ergibt sich für `BlogNav.astro`:
-- `blogSectionOff = onAboutSection` — Blog hidden auf allen About-Section-Seiten (inkl. Impressum, Datenschutz etc.)
-- `aboutOff = blogSelected` — About hidden nur auf Blog-Seiten
-- Auf Homepage und regulären Root-Seiten sind Blog und About beide sichtbar
+Der About-Seitenbaum ist statisch in `BlogNav.astro` als TypeScript-Konstante (`NavPage[]`) definiert. Sortiert nach weight:
-**About-Subtree:** Der About-Seitenbaum ist statisch in `BlogNav.astro` definiert (Typ `NavPage`), da About-Seiten keine Content Collection nutzen. Die `off`-Logik für Einträge innerhalb der Subtree spiegelt `tree.html` direkt: `aboutPageOff(page, parentUrl)` prüft `isCurrent || isAncestor || isSibling(!isNode) || isChild`. Section-Index-Seiten (About, Impressum) unterdrücken die Geschwister-Logik (`!currentAboutIsNode`).
-
-About-Seitenstruktur (aus `content/about/` im Hugo-Branch, sortiert nach weight):
```
/about.html
- /contact.html (weight 10)
- /impressum.html (weight 10, section-index)
+ /contact.html (weight 10)
+ /impressum.html (weight 10, section-index/isNode)
/urheberrechte.html (weight 10)
/datenschutz.html (weight 20)
/haftung-inhalte.html (weight 30)
/haftung-links.html (weight 40)
/google-analytics.html
- /agb.html (weight 30)
+ /agb.html (weight 30)
```
-Wenn Projects implementiert wird: `blogSectionOff |= onProjectsSection`, `aboutOff |= onProjectsSection`.
-
----
-
-#### URL-Kompatibilität via `url`-Frontmatter-Feld
-
-**Was:** Blog-Artikel können ein `url`-Feld im Frontmatter setzen (z.B. `url: /mein-alter-slug/`). Astro verwendet dieses, um die kanonische URL zu erzeugen, statt des Dateinamens.
+Section-Index-Seiten (`isNode=true`: About, Impressum) unterdrücken die Geschwister-Logik — auf `/impressum.html` sieht man nur die Impressum-Kinder, nicht die Geschwister von Impressum.
-**Warum:** Hugo generierte Blog-Post-URLs aus dem `url`-Frontmatter-Feld oder aus dem Slug, teilweise flach unter `/` (nicht unter `/blog/`). Um keine URLs zu brechen, wird `post.data.url ?? '/${post.id}/'` als Postlink verwendet. Die `getStaticPaths()`-Funktion in `[slug].astro` leitet die Route ebenfalls aus diesem Feld ab.
-
----
+### Projects-Bereich: Sonderregeln
-#### Word Count und Reading Time: Manuelle Berechnung aus Markdown-Body
+Der Projects-Bereich unterscheidet zwischen **sichtbaren** und **nicht sichtbaren** Projektversionen.
-**Was:** Wortanzahl und Lesezeit werden in Hilfsfunktionen `wordCount()` und aus `Math.ceil(wc / 200)` berechnet, indem Code-Blöcke aus dem Markdown-Body entfernt und der Rest auf Whitespace gesplittet wird.
-
-**Warum:** Astro stellt keine eingebaute Word-Count-API bereit. Alternativen wären Remark-Plugins, die beim Build laufen. Die Inline-Berechnung ist einfacher und ausreichend genau. Code-Blöcke werden herausgefiltert, weil sie die Lesezeit verzerren würden. Die 200-wpm-Rate entspricht Hugo's Standard (`reading_time_minutes = words / 200` gerundet aufwärts).
-
----
+**Sichtbare Versionen** (`params.current: true`): Die aktuell veröffentlichte Version eines Projekts. Hat eine kanonische URL direkt unter Root (z.B. `/hibernate-maven-plugin/`). Erscheint immer im Sitemap-Menü.
-#### Summary-Extraktion: Erste Nicht-Code-Paragraph bis 350 Zeichen
+**Nicht sichtbare Versionen** (`params.current: false`): Ältere oder zukünftige (SNAPSHOT-)Versionen. Haben URLs unter `/projects/PROJEKT_NAME/VERSION/`. `params.canonical` verweist auf die entsprechende Seite der sichtbaren Version.
-**Was:** `getSummary(body)` extrahiert eine Vorschau aus dem Markdown-Body, indem Code-Blöcke, HTML-Tags, Shortcodes, Links, Markdown-Formatierung und Überschriften entfernt werden und dann der erste Paragraph mit mindestens 20 Zeichen und maximal 350 Zeichen zurückgegeben wird.
-
-**Warum:** Hugo nutzt automatisch den ersten Paragraph als Summary. In Astro gibt es dafür keine eingebaute Funktion — bei Content Collections ist `post.body` der rohe Markdown-String. Statt eines Remark-Plugins wurde eine String-Manipulation gewählt, die für den vorhandenen Content ausreichend ist.
-
----
-
-#### `.html`-Endungen für Standalone-Seiten
-
-**Was:** Seiten wie About, Impressum und Datenschutz haben `.html`-Endungen in der URL (`/about.html`). In Astro wird das durch Benennung der Datei als `about.html.astro` erreicht.
-
-**Warum:** Diese URLs sind historisch aus dem WordPress-Import gewachsen und wurden in Hugo explizit mit `url: /about.html` beibehalten. Änderungen würden externe Links und Suchmaschinen-Indizes brechen.
-
----
-
-#### Standalone-Seiten: Navigation über BlogNav
-
-**Was:** Alle About-Section-Seiten (`about.html`, `impressum.html`, `datenschutz.html` sowie zukünftige wie `contact.html`, `agb.html` etc.) nutzen `BlogNav.astro` im `menu`-Slot. Die vollständige About-Subtree ist immer im HTML vorhanden; CSS blendet nicht relevante Einträge via `.off` aus.
-
-**Offener Punkt — Projects-Baum:** Hugo zeigt auf allen Seiten auch den Projects-Baum im Submenu (mit spezieller `current`-Flag-Logik für Projektversionen, aus `menu/tree.html`). Dieser fehlt noch. Sobald Projects implementiert wird, muss `BlogNav.astro` um den Projects-Ast erweitert werden.
-
----
+**Nav-Verhalten nicht sichtbarer Versionen:** Sie erscheinen nur dann im Sitemap-Menü, wenn man gerade auf einer Seite dieser Version ist — und zwar **an der Position der sichtbaren Version** (ersetzen diese im Nav). Alle Nav-Links zeigen auf die SNAPSHOT-URLs.
-#### RSS-Feed-Routing: Verzeichnisstruktur für korrekte URLs
+**Zweck:** Suchmaschinen-Robots entdecken über das Sitemap-Menü alle Versionen und werden via `<link rel="canonical">` auf die aktuelle Version verwiesen. Menschliche Besucher, die eine ältere Version aufrufen, sehen die originalen Inhalte mit einem Hinweis auf die aktuelle Version.
-**Was:** RSS-Feed-Routen für Taxonomie-Terms und Jahres-Archive liegen in Unterverzeichnissen: `src/pages/categories/[term]/index.xml.ts` statt `src/pages/categories/[term].xml.ts`.
+**Generierte Inhalte (apidocs, xref etc.):** Maven-Site-Outputs liegen fertig generiert in `public/projects/PROJEKT_NAME/VERSION/` und werden als statische Dateien ausgeliefert — ohne Astro-Layout, ohne HTML-Anpassungen, ohne Routing-Dateien in `src/pages/`. `src/lib/projects.ts` (`generatedDocNodes()`) erkennt diese Verzeichnisse zur Build-Zeit automatisch und fügt sie als Nav-Blatteinträge hinzu:
-**Warum:** Astro generiert aus `[term].xml.ts` die URL `/categories/TERM.xml`, aber die erwartete URL ist `/categories/TERM/index.xml` (analog zur HTML-Route `/categories/TERM/`). Die Verzeichnisstruktur `[term]/index.xml.ts` erzeugt korrekt `/categories/TERM/index.xml`. Dasselbe gilt für `tags/[term]/index.xml.ts` und `blog/archive/[year]/index.xml.ts`.
-
-**Generierte Feed-Routen:**
-- `/blog/index.xml` — alle Posts
-- `/blog/archive/index.xml` — alle Posts (identisch)
-- `/blog/archive/YYYY/index.xml` — Posts eines Jahres
-- `/categories/TERM/index.xml` — Posts einer Kategorie
-- `/tags/TERM/index.xml` — Posts eines Tags
-
----
-
-#### Shared Helpers: `src/lib/posts.ts`
-
-**Was:** Gemeinsam genutzte Hilfsfunktionen (`getAllPosts`, `postUrl`, `getSummary`, `wordCount`) sind in `src/lib/posts.ts` zentralisiert und werden von allen Seiten und API-Routen importiert.
-
-**Warum:** Ohne dieses Modul wäre `getCollection('blog')`, die Sortierlogik, `wordCount` und `getSummary` in allen Seiten-Dateien dupliziert worden (Blog-Index, Jahres-Archiv, Kategorie-Term, Tag-Term, Blog-Post, alle RSS-Feeds). Der Refactoring-Schritt wurde nach dem ersten Implementierungs-Durchlauf gemacht, als die Duplikation offensichtlich wurde.
-
----
-
-#### SEO: Description-Prop und OG-Tags in BaseLayout
-
-**Was:** `BaseLayout.astro` akzeptiert eine optionale `description`-Prop, die zu `<meta name="description">` und `<meta property="og:description">` wird. Zusätzlich werden `og:title`, `og:type` und `og:url` immer gerendert.
-
-**Warum:** Hugo rendert diese Meta-Tags automatisch aus dem Frontmatter. In Astro gibt es kein eingebautes SEO-System — es wurde bewusst darauf verzichtet, ein SEO-Integrations-Paket (z.B. `astro-seo`) einzuführen. Die wenigen benötigten Tags wurden direkt in `BaseLayout.astro` implementiert, da das einfacher und transparenter ist. Seiten ohne explizite `description`-Prop (z.B. Archiv-Seiten) rendern keine Description-Tags.
+| Verzeichnis | Nav-Bezeichnung |
+|---|---|
+| `apidocs/` | API-Docs |
+| `testapidocs/` | Test-API-Docs |
+| `xref/` | Quelltext |
+| `xref-test/` | Test-Quelltext |
---
-#### Projects: Content-Architektur und Anforderungen ans Tooling
+## Projects: Content-Architektur und Tooling-Anforderungen
Projektdokumentation liegt in `src/content/projects/PROJEKT_NAME/VERSION/` als HTML-Dateien mit YAML-Frontmatter. Ein Custom Loader in `src/content.config.ts` (mit `js-yaml`) liest diese ein; Routing und Nav-Logik werden in `src/lib/projects.ts` und `BlogNav.astro` verwaltet.
<ProjectPage entryId="PROJEKT_NAME/VERSION/relativer/pfad.html" />
```
-Die `entryId` ist der relative Pfad innerhalb von `src/content/projects/` (ohne dieses Präfix), also z.B. `"hibernate-maven-plugin/2.1.1/project-reports/_index.html"`.
+Die `entryId` ist der relative Pfad innerhalb von `src/content/projects/` ohne dieses Präfix, z.B. `"hibernate-maven-plugin/2.1.1/project-reports/_index.html"`.
-**`.html`-URL-Quirk:** `configuration.html.astro` erzeugt im Build `configuration.html/index.html` (Astro verwendet immer Directory-Style). Nginx serviert das korrekt via Directory-Index — identisches Verhalten wie bei den About-Seiten (`/contact.html` etc.).
+**`.html`-URL-Quirk:** `configuration.html.astro` erzeugt im Build `configuration.html/index.html` (Astro verwendet immer Directory-Style). Nginx serviert das korrekt via Directory-Index — identisches Verhalten wie bei den About-Seiten.
-**Generierte Inhalte** (apidocs, xref etc.): Der statische Maven-Site-Output wird direkt in `public/projects/PROJEKT_NAME/VERSION/` abgelegt — kein `src/content/`-Eintrag, keine Routing-Datei. `src/lib/projects.ts` (`generatedDocNodes()`) erkennt diese Verzeichnisse automatisch zur Build-Zeit und erzeugt Nav-Einträge.
+**Generierte Inhalte** werden direkt in `public/projects/PROJEKT_NAME/VERSION/` abgelegt — kein `src/content/`-Eintrag, keine Routing-Datei nötig (s.o. Nav-Erkennung via `generatedDocNodes()`).
-Erkannte Unterverzeichnisse in `public/projects/X/Y/`:
+---
-| Verzeichnis | Nav-Bezeichnung |
-|---|---|
-| `apidocs/` | API-Docs |
-| `testapidocs/` | Test-API-Docs |
-| `xref/` | Quelltext |
-| `xref-test/` | Test-Quelltext |
+## Architektur-Entscheidungen
----
+### CSS: Separater SCSS-Build
+
+SCSS-Quellen liegen in `src/styles/`. Sie werden über einen separaten `sass`-CLI-Aufruf kompiliert (`npm run build:css`); das kompilierte CSS landet in `public/css/` und ist im Git eingecheckt. Der `prebuild`/`predev`-Hook in `package.json` stellt sicher, dass CSS beim Build immer aktuell ist.
+
+SCSS-Änderungen erfordern `npm run build:css` oder einen Dev-Server-Neustart; HMR für SCSS funktioniert nicht automatisch.
+
+### Taxonomy-URL-Struktur
+
+Taxonomy-Seiten sind auf zwei URL-Ebenen verteilt:
+- Index-Seiten: `/blog/categories/` und `/blog/tags/` (unter dem Blog-Pfad)
+- Term-Seiten: `/categories/TERM/` und `/tags/TERM/` (direkt unter Root)
+
+### `.html`-Endungen für Standalone-Seiten
+
+Seiten wie About, Impressum und Projekt-Unterseiten haben `.html`-Endungen in der URL. In Astro wird das durch Benennung der Datei als `page.html.astro` erreicht. Astro erzeugt dabei `page.html/index.html` im Build-Output (Directory-Style). Nginx serviert das korrekt via Directory-Index.
+
+### URL via `url`-Frontmatter-Feld
+
+Blog-Artikel und Projekt-Seiten können ein `url`-Feld im Frontmatter setzen. Astro verwendet dieses für die kanonische URL statt des Dateinamens (`post.data.url ?? '/${post.id}/'`). Die `getStaticPaths()`-Funktion in `[slug].astro` leitet die Route daraus ab.
+
+### Shared Helpers
+
+- `src/lib/posts.ts` — `getAllPosts`, `postUrl`, `getSummary`, `wordCount` für Blog-Seiten und RSS-Feeds
+- `src/lib/projects.ts` — `getActiveProject`, `getVisibleProjectNavTrees`, `buildNavTree`, `generatedDocNodes` für die Projects-Nav-Logik in `BlogNav.astro`
+
+### Content Collections
+
+- **Blog:** `src/content/blog/` — Markdown-Dateien, `glob()`-Loader, Zod-Schema-Validierung
+- **Projects:** `src/content/projects/` — HTML-Dateien mit YAML-Frontmatter, Custom Loader in `src/content.config.ts` (nutzt `js-yaml` für Frontmatter-Parsing)
+
+### SEO-Meta-Tags
+
+`BaseLayout.astro` rendert `<meta name="description">`, `og:title`, `og:type`, `og:url`, `og:description` (optional über `description`-Prop). Eine optionale `canonical`-Prop erzeugt `<link rel="canonical">` — wird für nicht sichtbare Projektversionen genutzt, die auf die sichtbare Version verweisen.
+
+### RSS-Feed-Routing
+
+Feed-Routen liegen in Unterverzeichnissen (`src/pages/categories/[term]/index.xml.ts` statt `src/pages/categories/[term].xml.ts`), weil Astro sonst `/categories/TERM.xml` statt `/categories/TERM/index.xml` erzeugen würde.
+
+Generierte Feeds:
+- `/blog/index.xml` und `/blog/archive/index.xml` — alle Posts
+- `/blog/archive/YYYY/index.xml` — Posts eines Jahres
+- `/categories/TERM/index.xml` — Posts einer Kategorie
+- `/tags/TERM/index.xml` — Posts eines Tags
+
+### Word Count und Reading Time
+
+`wordCount()` entfernt Code-Blöcke aus dem Markdown-Body und splittet auf Whitespace. Lesezeit = `Math.ceil(wc / 200)` (200 wpm, aufgerundet).
+
+### Summary-Extraktion
-#### Hugo-Dateien entfernt nach Migration
-
-**Was:** Nach Abschluss der Astro-Migration wurden folgende Hugo-spezifische Dateien und Verzeichnisse aus dem Repo entfernt:
-- `content/` (1288 Blog-Artikel und Seiten — nun in `src/content/blog/`)
-- `themes/PaperMod/` (Hugo-Theme-Submodul)
-- `themes/hugo-juplo/` (das eigene Hugo-Theme-Submodul)
-- `hugo.yaml` (Hugo-Konfiguration)
-- `archetypes/` (Hugo-Vorlagen für neue Inhalte)
-- `inventory.md` (Hilfsdatei zur Analyse während der Migration)
-- `.gitmodules` (Submodul-Konfiguration, da keine Submodule mehr vorhanden)
-
-**Behalten:**
-- `data/` (enthält echte Nutzer-Kommentardaten, kein Hugo-spezifischer Content)
-- `docker-compose.yml` und `nginx.conf` (Deployment-Konfiguration, unabhängig vom SSG)
-- `public/` (statische Assets, bereits im Astro-Format)
+`getSummary(body)` gibt den ersten Nicht-Code-Paragraph mit 20–350 Zeichen zurück. Code-Blöcke, HTML-Tags, Shortcodes, Links, Markdown-Formatierung und Überschriften werden vor der Extraktion entfernt.