@conciso/design-system-mcp 2.6.0 → 2.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (21) hide show
  1. package/README.md +1 -0
  2. package/package.json +2 -2
  3. package/snapshot/manifests/components.json +15 -15
  4. package/snapshot/services/addon-docs/mdx/grundlagen-barrierefreiheit--/303/274bersicht.json +1 -1
  5. package/snapshot/services/addon-docs/mdx/komponenten-call-to-action-cta-band.json +1 -1
  6. package/snapshot/services/core/docgen/komponenten-call-to-action-cta-band.json +1 -1
  7. package/snapshot/services/core/docgen/komponenten-cards-teaser-featured-karte.json +1 -1
  8. package/snapshot/services/core/docgen/komponenten-cards-teaser-icon-karte.json +1 -1
  9. package/snapshot/services/core/docgen/komponenten-hero-hero-bild.json +1 -1
  10. package/snapshot/services/core/docgen/komponenten-hero-st/303/266rer.json +1 -1
  11. package/snapshot/services/core/docgen/komponenten-tabelle-tabelle.json +1 -1
  12. package/snapshot/services/core/docgen/komponenten-tabelle-vergleichstabelle.json +1 -1
  13. package/snapshot/services/core/docgen/seitenmuster-seminar-/302/267-training-fakten-liste.json +1 -1
  14. package/snapshot/services/core/docgen/seitenmuster-wissensbeitrag-article-header.json +1 -1
  15. package/snapshot/services/core/docgen/seitenmuster-wissensbeitrag-author-card.json +1 -1
  16. package/snapshot/services/core/docgen/seitenmuster-wissensbeitrag-avatar.json +1 -1
  17. package/snapshot/services/core/docgen/seitenmuster-wissensbeitrag-callout.json +1 -1
  18. package/snapshot/services/core/docgen/seitenmuster-wissensbeitrag-figure.json +1 -1
  19. package/snapshot/services/core/docgen/seitenmuster-wissensbeitrag-inhaltsverzeichnis.json +1 -1
  20. package/snapshot/services/core/docgen/seitenmuster-wissensbeitrag-pull-quote.json +1 -1
  21. package/src/instructions.mjs +10 -1
package/README.md CHANGED
@@ -34,6 +34,7 @@ Beim Verbinden gibt der Server dem Assistenten außerdem feste Regeln mit:
34
34
  3. Nur Inputs und Outputs verwenden, die `docs-show` liefert. Vorher nachsehen, nie raten.
35
35
  4. Bei Fragen zur Einrichtung die Storybook-Seite „Einrichtung“ lesen.
36
36
  5. Für Auswahl- und Gestaltungsfragen zusätzlich die Verwendungsguidance der Komponente heranziehen, sonst über `docs-list` die passende „Verwendung“-Seite suchen.
37
+ 6. Mehrere Doku-Seiten in `docs-list` heißen gleich (meist „Übersicht“ oder „Verwendung“), unterscheidbar nur über ihre ID (Schema „`<pfad>--<name>`“).
37
38
 
38
39
  Weitere Eigenschaften:
39
40
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conciso/design-system-mcp",
3
- "version": "2.6.0",
3
+ "version": "2.7.1",
4
4
  "description": "MCP-Server für Consumer des Conciso Design System, beantwortet docs-list, docs-show und docs-show-story über stdio aus einem mitgelieferten Storybook-Snapshot, ohne Netzzugriff.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -29,7 +29,7 @@
29
29
  "prepack": "node scripts/prepack.mjs",
30
30
  "test": "node --test test/*.test.mjs",
31
31
  "test:smoke": "node scripts/smoke-test.mjs",
32
- "test:eval-checker": "node --test eval/checker.test.mjs",
32
+ "test:eval-checker": "node --test eval/checker.test.mjs eval/run-eval.test.mjs",
33
33
  "eval": "node eval/run-eval.mjs",
34
34
  "lint": "eslint ."
35
35
  },
@@ -317,7 +317,7 @@
317
317
  "komponenten-cards-teaser-featured-karte": {
318
318
  "id": "komponenten-cards-teaser-featured-karte",
319
319
  "name": "FeaturedCardComponent",
320
- "description": "FeaturedCard (cds-featured-card) — Wrapper um `.card-featured` aus\ncss/components.css (css/components.css:230–249): die horizontale Großkarte für\n**genau einen** hervorgehobenen Beitrag oder Termin (Bild links 60 %, Textspalte\nrechts 40 % als absolut positioniertes Overlay), siehe `docs/index.html`\nAbschnitt „Featured · horizontale Großkarte“ (`#gt-events-featured`).\n\n**Entscheidung 1 — `href` entscheidet zwischen `<a>` und `<article>`.** Die\nDoku-Beispiele zeigen die Featured-Card ausschließlich als Link\n(`a.card.card-elevated.card-featured`), das Ticket verlangt aber ausdrücklich eine\nVariante ohne `<a>`. `.card-elevated` ist nach `CONTRIBUTING.md` §4 („Elevation =\nInteraktivität“) im CSS auf `a.card-elevated` gescoped — eine `<article>` bekäme\nden Schatten mit gesetzter Klasse ohnehin nicht. Die Komponente führt die Klasse\ndeshalb nur mit, wenn sie auch wirkt: mit `href` ein `<a class=\"card card-elevated\ncard-featured\">`, ohne `href` ein `<article class=\"card card-featured\">` (ruht\nflach mit dem `--bd-strong`-Rahmen aus `.card`, wie jede statische Fläche). Media\nund Body stehen dafür einmal in einem gemeinsamen `<ng-template>` und werden in\nbeiden Zweigen per `<ng-container [ngTemplateOutlet]>` eingesetzt — dasselbe\nMuster wie `SectionComponent` (`section.component.ts`) für „zwei Wurzelelemente,\nein Körper“. `<ng-container>` rendert selbst kein Element, die Kindselektoren aus\nEntscheidung 2 bleiben also unberührt: im laufenden Storybook erneut per\n`querySelector`/`getBoundingClientRect` gemessen (siehe Play-Funktionen), Ergebnis\nunverändert zur vorherigen, noch duplizierten Fassung.\n\n**Entscheidung 2 — die Pill ist direkt komponiertes Markup, kein `<cds-pill>`.**\nDas CSS arbeitet mit direkten Kindselektoren: `.card-featured>.card-media`,\n`.card-featured-body>.pill`, `.card-featured-body>.card-text`,\n`.card-featured-body>.card-title-hero` (jeweils mit `margin`/`min-height`/\n`line-clamp`-Übernahmen, siehe CSS-Kommentare dort). `<cds-pill>` rendert sein\n`.pill`-`<span>` nicht auf dem eigenen Host-Element, sondern eine Ebene tiefer\n(`pill.component.ts`: Host ist `<cds-pill>`, `.pill` sitzt im Template darunter) —\nprojiziert als `<cds-pill class=\"pill\">` stünde die Pille damit als\n`.card-featured-body>cds-pill>.pill`, zwei Ebenen statt einer, und die\nKindselektoren griffen nicht mehr (leise: kein Fehler, nur falsches Layout).\nDieselbe Abwägung wie in `download-cta.component.ts` (Kommentar über den\nButtons): dort verhindert `.cta-dl-actions`, das seine Kinder per Flex streckt,\ndass ein `<cds-button>`-Host mitstreckt; hier verhindert ein Kindselektor, dass\nirgendein zusätzliches Host-Element dazwischentritt. In beiden Fällen werden die\nKlassen direkt komponiert statt eine Wrapper-Komponente zu verwenden. Geprüft im\nlaufenden Storybook (`Komponenten/Cards & Teaser/Featured-Karte`): die gerenderte\nKette ist `.card-featured>.card-media` und `.card-featured-body` mit `.pill`,\n`.card-text`, `.card-title-hero` als direkten Kindern, kein zusätzliches Element\ndazwischen (siehe Play-Funktionen der Stories).\n\nWeil die Pille direkt komponiert wird, übernimmt diese Komponente auch die\n`aria-label`-Regel von `PillComponent` (`pill.component.ts`) von Hand nach:\nDefault „Bereich `<pill>`“, überschreibbar über `pillAriaLabel` — nötig, weil die\nPille nicht immer einen Bereich benennt (siehe `pillAriaLabel`-Doku unten).\n\n**Entscheidung 3 — kein separates `data-area` für die Pille.** Die Doku setzt\nKarte und Pille immer auf denselben Bereich (`docs/index.html:8979`: Karte\n`data-area=\"es\"`, Pille `data-area=\"es\"`). Die Komponente bindet deshalb `area`\nauf beide, statt einen zweiten Bereichs-Input zu erfinden.\n\n**Entscheidung 4 — kein CTA- und kein Meta-Input.** Die Doku-Vorlage zeigt\nzusätzlich einen Meta-Strip (Datum/Ort/Format) und einen `.card-cta-link`-Fuß;\ndas Ticket benennt als API aber nur `title`/`text`/`imageSrc`/`imageAlt`/`href`/\n`pill`/`area`. Diese Komponente bildet exakt diese Fläche ab, ohne Inputs zu\nerfinden, die das Ticket nicht vorsieht — ein Meta-Strip oder ein CTA-Fuß wären\neine spätere, eigene Erweiterung.\n\nVerwendungsguidance dieser Gruppe: siehe Card (`komponenten-cards-teaser-card--verwendung`).",
320
+ "description": "FeaturedCard (cds-featured-card) — Wrapper um `.card-featured` aus\ncss/components.css (css/components.css:230–249): die horizontale Großkarte für\n**genau einen** hervorgehobenen Beitrag oder Termin (Bild links 60 %, Textspalte\nrechts 40 % als absolut positioniertes Overlay), siehe `docs/index.html`\nAbschnitt „Featured · horizontale Großkarte“ (`#gt-events-featured`).\n\n**Entscheidung 1 — `href` entscheidet zwischen `<a>` und `<article>`.** Die\nDoku-Beispiele zeigen die Featured-Card ausschließlich als Link\n(`a.card.card-elevated.card-featured`), diese Komponente unterstützt aber ausdrücklich eine\nVariante ohne `<a>`. `.card-elevated` ist nach `CONTRIBUTING.md` §4 („Elevation =\nInteraktivität“) im CSS auf `a.card-elevated` gescoped — eine `<article>` bekäme\nden Schatten mit gesetzter Klasse ohnehin nicht. Die Komponente führt die Klasse\ndeshalb nur mit, wenn sie auch wirkt: mit `href` ein `<a class=\"card card-elevated\ncard-featured\">`, ohne `href` ein `<article class=\"card card-featured\">` (ruht\nflach mit dem `--bd-strong`-Rahmen aus `.card`, wie jede statische Fläche). Media\nund Body stehen dafür einmal in einem gemeinsamen `<ng-template>` und werden in\nbeiden Zweigen per `<ng-container [ngTemplateOutlet]>` eingesetzt — dasselbe\nMuster wie `SectionComponent` (`section.component.ts`) für „zwei Wurzelelemente,\nein Körper“. `<ng-container>` rendert selbst kein Element, die Kindselektoren aus\nEntscheidung 2 bleiben also unberührt: im laufenden Storybook erneut per\n`querySelector`/`getBoundingClientRect` gemessen (siehe Play-Funktionen), Ergebnis\nunverändert zur vorherigen, noch duplizierten Fassung.\n\n**Entscheidung 2 — die Pill ist direkt komponiertes Markup, kein `<cds-pill>`.**\nDas CSS arbeitet mit direkten Kindselektoren: `.card-featured>.card-media`,\n`.card-featured-body>.pill`, `.card-featured-body>.card-text`,\n`.card-featured-body>.card-title-hero` (jeweils mit `margin`/`min-height`/\n`line-clamp`-Übernahmen, siehe CSS-Kommentare dort). `<cds-pill>` rendert sein\n`.pill`-`<span>` nicht auf dem eigenen Host-Element, sondern eine Ebene tiefer\n(`pill.component.ts`: Host ist `<cds-pill>`, `.pill` sitzt im Template darunter) —\nprojiziert als `<cds-pill class=\"pill\">` stünde die Pille damit als\n`.card-featured-body>cds-pill>.pill`, zwei Ebenen statt einer, und die\nKindselektoren griffen nicht mehr (leise: kein Fehler, nur falsches Layout).\nDieselbe Abwägung wie in `download-cta.component.ts` (Kommentar über den\nButtons): dort verhindert `.cta-dl-actions`, das seine Kinder per Flex streckt,\ndass ein `<cds-button>`-Host mitstreckt; hier verhindert ein Kindselektor, dass\nirgendein zusätzliches Host-Element dazwischentritt. In beiden Fällen werden die\nKlassen direkt komponiert statt eine Wrapper-Komponente zu verwenden. Geprüft im\nlaufenden Storybook (`Komponenten/Cards & Teaser/Featured-Karte`): die gerenderte\nKette ist `.card-featured>.card-media` und `.card-featured-body` mit `.pill`,\n`.card-text`, `.card-title-hero` als direkten Kindern, kein zusätzliches Element\ndazwischen (siehe Play-Funktionen der Stories).\n\nWeil die Pille direkt komponiert wird, übernimmt diese Komponente auch die\n`aria-label`-Regel von `PillComponent` (`pill.component.ts`) von Hand nach:\nDefault „Bereich `<pill>`“, überschreibbar über `pillAriaLabel` — nötig, weil die\nPille nicht immer einen Bereich benennt (siehe `pillAriaLabel`-Doku unten).\n\n**Entscheidung 3 — kein separates `data-area` für die Pille.** Die Doku setzt\nKarte und Pille immer auf denselben Bereich (`docs/index.html:8979`: Karte\n`data-area=\"es\"`, Pille `data-area=\"es\"`). Die Komponente bindet deshalb `area`\nauf beide, statt einen zweiten Bereichs-Input zu erfinden.\n\n**Entscheidung 4 — kein CTA- und kein Meta-Input.** Die Doku-Vorlage zeigt\nzusätzlich einen Meta-Strip (Datum/Ort/Format) und einen `.card-cta-link`-Fuß;\ndiese Komponente benennt als API aber nur `title`/`text`/`imageSrc`/`imageAlt`/`href`/\n`pill`/`area`. Diese Komponente bildet exakt diese Fläche ab, ohne zusätzliche Inputs zu\nerfinden — ein Meta-Strip oder ein CTA-Fuß wären\neine spätere, eigene Erweiterung.\n\nVerwendungsguidance dieser Gruppe: siehe Card (`komponenten-cards-teaser-card--verwendung`).",
321
321
  "docgen": {
322
322
  "$ref": "../services/core/docgen/komponenten-cards-teaser-featured-karte.json#/components/komponenten-cards-teaser-featured-karte"
323
323
  },
@@ -328,7 +328,7 @@
328
328
  "komponenten-cards-teaser-icon-karte": {
329
329
  "id": "komponenten-cards-teaser-icon-karte",
330
330
  "name": "IconCardComponent",
331
- "description": "IconCard (`[cdsIconCard]`) — Wrapper um `.ep-card` aus css/components.css\n(css/components.css:1298–1340): die Icon-Kachel mit farbiger Icon-Fläche, Eyebrow,\nTitel, Text und optionaler Pfeil-CTA-Zeile, die auf den Beispielseiten Bereichs-\nund Angebots-Einstiege trägt (`docs/index.html:9401–9420` u. a., 81 Vorkommen).\n\n**Entscheidung 1 — Attributselektor, kein Element-Selektor.** Erste Fassung dieser\nKomponente war `cds-icon-card` als eigenes Element mit `href`-Input, das intern\nzwischen `<a class=\"ep-card ep-card-link\">` und `<div class=\"ep-card\">` wählte\n(`@if`/`@else` + `<ng-template>`). Gemessen im laufenden Storybook: zwei Karten mit\nunterschiedlich langem Text ergaben im `.ep-cards`-Grid ungleiche `.ep-card`-Höhen\n(174px/270px), weil `align-items:stretch` nur den unsichtbaren `<cds-icon-card>`-Host\nstreckt, nicht das `.ep-card`-Element eine Ebene darunter. Dieselbe Messung mit\n`.ep-card` OHNE Host-Wrapper (rohes Markup direkt in `.ep-cards`) ergab 306px/306px,\nCTA-Unterkanten auf identischer Y-Koordinate — das CSS gleicht die Höhen korrekt aus,\nsobald `.ep-card` selbst das Grid-Kind ist. Der Fehler saß also im Wrapper-Element,\nnicht in der CSS-Schicht (siehe „Im Raster“-Story, dort dieselbe Messung als\nPlay-Funktion gepinnt).\n\nDie Lösung, analog zu Angular Materials `a[mat-button], button[mat-button]`: die\nKomponente hängt sich als Attribut an ein vom Konsumenten geschriebenes `<a>` oder\n`<div>`, statt ein eigenes Element zu sein. Dadurch trägt **derselbe** native Knoten,\nden der Konsument in sein Grid schreibt, sowohl die Komponentenlogik als auch die\n`.ep-card`-Klasse — kein Host-Element dazwischen, `align-items:stretch` trifft direkt\nauf `.ep-card`.\n\n**`href` ist deshalb kein Input mehr.** Ob die Karte ein Link ist, entscheidet der\nKonsument über das gewählte Tag (`<a cdsIconCard href=\"…\">` vs. `<div cdsIconCard>`)\nund setzt `href` als natives Attribut direkt am `<a>` — Angular muss dafür nichts\nverwalten. `.ep-card-link` (nur auf `a.ep-card-link` wirksam, `CONTRIBUTING.md` §4)\nwird deshalb vom Host-Tag abgeleitet, nicht mehr per Verzweigung im Template\ngerendert.\n\n**`.ep-card-link` hängt an `<a>` UND `href`, nicht am Tag allein.** Ein `<a\ncdsIconCard>` ohne `href` ist weder fokussierbar noch hat es eine Link-Rolle —\n`.ep-card-link` (Schatten, Hover-Anhebung, Cursor) nur am Tag festzumachen hätte\neine Karte erzeugt, die aussieht wie ein Link, aber keiner ist, und genau die\nGarantie aufgegeben, die die frühere `href`-Input-Fassung strukturell hatte (leerer\nString → zwingend der `<div>`-Zweig). `isLink()` prüft deshalb beides.\n\n**`isLink()` ist eine Methode im Host-Binding, kein einmalig berechnetes Feld.**\n`href` kann nach dem Erstellen zur Laufzeit gesetzt/entfernt werden (z. B.\n`<a cdsIconCard [attr.href]=\"urlSignal()\">`). Ein Host-Binding-Ausdruck\n(`'[class.ep-card-link]': 'isLink()'`) wird bei JEDEM Refresh der DECLARING VIEW\nneu ausgewertet — also der Elternvorlage, die `<a cdsIconCard>` schreibt —, nicht\nnur bei einem Refresh der eigenen (OnPush-gesteuerten) Kindvorlage dieser\nKomponente. Das ist kein Sonderfall wie bei einem in ADR-0007 §3 verworfenen\nGetter (dort ging es um einen Getter in einer INTERPOLATION der eigenen\nKindvorlage, die unter OnPush nur bei eigenen Signal-Änderungen neu läuft):\nHost-Bindings werden unabhängig vom OnPush-Status der Komponente selbst\nmitgeführt, wenn die Elternansicht aktualisiert wird — reagiert also auf\n`[attr.href]`/`[href]`-Bindungen des Konsumenten, ob Signal- oder\nZone.js-getrieben, ohne `effect()` (ADR-0007 §4) und ohne `MutationObserver`.\nEmpirisch geprüft (Spike-Story mit `[attr.href]=\"signal()\"`-Toggle zur Laufzeit,\nseither wieder gelöscht): Setzen des Signals nach Erstrender schaltet\n`.ep-card-link` zuverlässig um. Einzige Grenze: eine Änderung, die AUSSERHALB von\nAngular direkt am nativen Element vorgenommen wird (z. B. `el.setAttribute\n('href', …)` von Fremdcode), löst keinen Refresh aus — das gilt aber für jede\nAngular-Bindung gleichermaßen und ist kein Sonderfall dieser Komponente.\n\n**Entscheidung 2 — Icon als projizierter Inhalt, OHNE dass das SVG eine eigene\nGrößenklasse tragen muss.** Wie bei `cds-stoerer` (siehe dessen Klassendoku) sind\ndie Icons auf den Beispielseiten wechselnde Heroicons, keine DS-Bereichsglyphen aus\nder Registry `icons/cds-icons.ts` — Inhalt, nicht Chrom. Deshalb Projektion über\n`<ng-content select=\"[cdsIcon]\">` statt eines `icon`-Strings. Anders als beim Störer\nist `.ep-card-icon` hier aber ein echter CONTAINER: css/components.css:1310+1318\nsetzt Maße und Stroke über den Nachfahren-Selektor `.ep-card-icon svg{width:32px;\nheight:32px;stroke-width:var(--icon-stroke-sm)}`, nicht über eine Klasse auf dem\nSVG selbst. Der Konsument liefert deshalb ein unverändertes `<svg cdsIcon\nviewBox=\"…\" aria-hidden=\"true\" focusable=\"false\">…</svg>` OHNE zusätzliche Klasse —\ndas Verhältnis Größe/Stroke kommt allein aus dem Container. Geprüft an\ncss/components.css:1298–1318 (Kommentar dort: „Farbe kommt aus der Komponente,\nnicht aus … inline gesetzten style-Attributen“) und in der Story „Interaktiv“\ngepinnt (Play-Funktion misst die gerenderte SVG-Breite).\n\n**`data-area` sitzt auf drei Elementen** (`.ep-card` (Host), `.ep-card-icon`,\n`.ep-card-eyebrow`, wie im Mockup, `docs/index.html:4681–4686`): die CTA-Farbe\nbraucht dagegen KEIN eigenes `data-area`, sie kommt über den Nachfahren-Selektor\n`.ep-card[data-area=\"…\"] .ep-card-cta` (css/components.css:1336–1339) vom\n`data-area` der Karte selbst.\n\n**Kein erfundener `aria-label`.** Der Pfeil-Suffix der CTA-Zeile ist reine visuelle\nAffordanz (`docs/index.html:4686`: `<span aria-hidden=\"true\">→</span>`) und steht\ndeshalb `aria-hidden`, unverändert zum Mockup — kein zusätzlicher, selbst\nausgedachter zugänglicher Name.\n\n**Entscheidung 3 — kein `cdsIconCards`-Raster.** `.ep-cards` ist ein reines\n`display:grid` ohne Struktur oder Verhalten (dieselbe Begründung wie beim Verzicht\nauf einen `layout-grid`-Wrapper, siehe `spec.md`). Konsumenten schreiben\n`<div class=\"ep-cards\">` von Hand (siehe Story „Im Raster“).\n\nVerwendungsguidance dieser Gruppe: siehe Card (`komponenten-cards-teaser-card--verwendung`).",
331
+ "description": "IconCard (`[cdsIconCard]`) — Wrapper um `.ep-card` aus css/components.css\n(css/components.css:1298–1340): die Icon-Kachel mit farbiger Icon-Fläche, Eyebrow,\nTitel, Text und optionaler Pfeil-CTA-Zeile, die auf den Beispielseiten Bereichs-\nund Angebots-Einstiege trägt (`docs/index.html:9401–9420` u. a., 81 Vorkommen).\n\n**Entscheidung 1 — Attributselektor, kein Element-Selektor.** Erste Fassung dieser\nKomponente war `cds-icon-card` als eigenes Element mit `href`-Input, das intern\nzwischen `<a class=\"ep-card ep-card-link\">` und `<div class=\"ep-card\">` wählte\n(`@if`/`@else` + `<ng-template>`). Gemessen im laufenden Storybook: zwei Karten mit\nunterschiedlich langem Text ergaben im `.ep-cards`-Grid ungleiche `.ep-card`-Höhen\n(174px/270px), weil `align-items:stretch` nur den unsichtbaren `<cds-icon-card>`-Host\nstreckt, nicht das `.ep-card`-Element eine Ebene darunter. Dieselbe Messung mit\n`.ep-card` OHNE Host-Wrapper (rohes Markup direkt in `.ep-cards`) ergab 306px/306px,\nCTA-Unterkanten auf identischer Y-Koordinate — das CSS gleicht die Höhen korrekt aus,\nsobald `.ep-card` selbst das Grid-Kind ist. Der Fehler saß also im Wrapper-Element,\nnicht in der CSS-Schicht (siehe „Im Raster“-Story, dort dieselbe Messung als\nPlay-Funktion gepinnt).\n\nDie Lösung, analog zu Angular Materials `a[mat-button], button[mat-button]`: die\nKomponente hängt sich als Attribut an ein vom Konsumenten geschriebenes `<a>` oder\n`<div>`, statt ein eigenes Element zu sein. Dadurch trägt **derselbe** native Knoten,\nden der Konsument in sein Grid schreibt, sowohl die Komponentenlogik als auch die\n`.ep-card`-Klasse — kein Host-Element dazwischen, `align-items:stretch` trifft direkt\nauf `.ep-card`.\n\n**`href` ist deshalb kein Input mehr.** Ob die Karte ein Link ist, entscheidet der\nKonsument über das gewählte Tag (`<a cdsIconCard href=\"…\">` vs. `<div cdsIconCard>`)\nund setzt `href` als natives Attribut direkt am `<a>` — Angular muss dafür nichts\nverwalten. `.ep-card-link` (nur auf `a.ep-card-link` wirksam, `CONTRIBUTING.md` §4)\nwird deshalb vom Host-Tag abgeleitet, nicht mehr per Verzweigung im Template\ngerendert.\n\n**`.ep-card-link` hängt an `<a>` UND `href`, nicht am Tag allein.** Ein `<a\ncdsIconCard>` ohne `href` ist weder fokussierbar noch hat es eine Link-Rolle —\n`.ep-card-link` (Schatten, Hover-Anhebung, Cursor) nur am Tag festzumachen hätte\neine Karte erzeugt, die aussieht wie ein Link, aber keiner ist, und genau die\nGarantie aufgegeben, die die frühere `href`-Input-Fassung strukturell hatte (leerer\nString → zwingend der `<div>`-Zweig). `isLink()` prüft deshalb beides.\n\n**`isLink()` ist eine Methode im Host-Binding, kein einmalig berechnetes Feld.**\n`href` kann nach dem Erstellen zur Laufzeit gesetzt/entfernt werden (z. B.\n`<a cdsIconCard [attr.href]=\"urlSignal()\">`). Ein Host-Binding-Ausdruck\n(`'[class.ep-card-link]': 'isLink()'`) wird bei JEDEM Refresh der DECLARING VIEW\nneu ausgewertet — also der Elternvorlage, die `<a cdsIconCard>` schreibt —, nicht\nnur bei einem Refresh der eigenen (OnPush-gesteuerten) Kindvorlage dieser\nKomponente. Das ist kein Sonderfall wie bei einem in ADR-0007 §3 verworfenen\nGetter (dort ging es um einen Getter in einer INTERPOLATION der eigenen\nKindvorlage, die unter OnPush nur bei eigenen Signal-Änderungen neu läuft):\nHost-Bindings werden unabhängig vom OnPush-Status der Komponente selbst\nmitgeführt, wenn die Elternansicht aktualisiert wird — reagiert also auf\n`[attr.href]`/`[href]`-Bindungen des Konsumenten, ob Signal- oder\nZone.js-getrieben, ohne `effect()` (ADR-0007 §4) und ohne `MutationObserver`.\nEmpirisch geprüft (Spike-Story mit `[attr.href]=\"signal()\"`-Toggle zur Laufzeit,\nseither wieder gelöscht): Setzen des Signals nach Erstrender schaltet\n`.ep-card-link` zuverlässig um. Einzige Grenze: eine Änderung, die AUSSERHALB von\nAngular direkt am nativen Element vorgenommen wird (z. B. `el.setAttribute\n('href', …)` von Fremdcode), löst keinen Refresh aus — das gilt aber für jede\nAngular-Bindung gleichermaßen und ist kein Sonderfall dieser Komponente.\n\n**Entscheidung 2 — Icon als projizierter Inhalt, OHNE dass das SVG eine eigene\nGrößenklasse tragen muss.** Wie bei `cds-stoerer` (siehe dessen Klassendoku) sind\ndie Icons auf den Beispielseiten wechselnde Heroicons, keine DS-Bereichsglyphen aus\nder Registry `icons/cds-icons.ts` — Inhalt, nicht Chrom. Deshalb Projektion über\n`<ng-content select=\"[cdsIcon]\">` statt eines `icon`-Strings. Anders als beim Störer\nist `.ep-card-icon` hier aber ein echter CONTAINER: css/components.css:1310+1318\nsetzt Maße und Stroke über den Nachfahren-Selektor `.ep-card-icon svg{width:32px;\nheight:32px;stroke-width:var(--icon-stroke-sm)}`, nicht über eine Klasse auf dem\nSVG selbst. Der Konsument liefert deshalb ein unverändertes `<svg cdsIcon\nviewBox=\"…\" aria-hidden=\"true\" focusable=\"false\">…</svg>` OHNE zusätzliche Klasse —\ndas Verhältnis Größe/Stroke kommt allein aus dem Container. Geprüft an\ncss/components.css:1298–1318 (Kommentar dort: „Farbe kommt aus der Komponente,\nnicht aus … inline gesetzten style-Attributen“) und in der Story „Interaktiv“\ngepinnt (Play-Funktion misst die gerenderte SVG-Breite).\n\n**`data-area` sitzt auf drei Elementen** (`.ep-card` (Host), `.ep-card-icon`,\n`.ep-card-eyebrow`, wie im Mockup, `docs/index.html:4681–4686`): die CTA-Farbe\nbraucht dagegen KEIN eigenes `data-area`, sie kommt über den Nachfahren-Selektor\n`.ep-card[data-area=\"…\"] .ep-card-cta` (css/components.css:1336–1339) vom\n`data-area` der Karte selbst.\n\n**Kein erfundener `aria-label`.** Der Pfeil-Suffix der CTA-Zeile ist reine visuelle\nAffordanz (`docs/index.html:4686`: `<span aria-hidden=\"true\">→</span>`) und steht\ndeshalb `aria-hidden`, unverändert zum Mockup — kein zusätzlicher, selbst\nausgedachter zugänglicher Name.\n\n**Entscheidung 3 — kein `cdsIconCards`-Raster.** `.ep-cards` ist ein reines\n`display:grid` ohne Struktur oder Verhalten (dieselbe Begründung wie beim Verzicht\nauf einen `layout-grid`-Wrapper). Konsumenten schreiben\n`<div class=\"ep-cards\">` von Hand (siehe Story „Im Raster“).\n\nVerwendungsguidance dieser Gruppe: siehe Card (`komponenten-cards-teaser-card--verwendung`).",
332
332
  "docgen": {
333
333
  "$ref": "../services/core/docgen/komponenten-cards-teaser-icon-karte.json#/components/komponenten-cards-teaser-icon-karte"
334
334
  },
@@ -383,7 +383,7 @@
383
383
  "komponenten-call-to-action-cta-band": {
384
384
  "id": "komponenten-call-to-action-cta-band",
385
385
  "name": "CtaBandComponent",
386
- "description": "CtaBand (`[cdsCtaBand]`) — Wrapper um `.ep-cta-band` aus css/components.css\n(css/components.css:1456, Kopf-Duo `.ep-cta-h2`/`.ep-cta-sub` ebenda): das\nbereichsgefärbte Page-End-CTA-Band, das laut Doku-Site (`docs/index.html:6237`,\nAnker `gt-cta-band`) am Ende jeder Customer-Page direkt vor dem Footer steht — die\n„letzte Einladung“, wenn die Nutzerin die Seite durchgelesen hat. Ausgezählt: 22\nVorkommen auf den Beispielseiten, ausnahmslos als `<div class=\"ep-cta-band\"\nstyle=\"background:…\">` mit genau einer Aktion.\n\n**Entscheidung 1 — Attributselektor, kein eigenes Element (ADR-0008, derselbe\n„Fläche am Host“-Fall wie `cds-section`).** `css/components.css` trägt für\n`.ep-cta-band` KEIN `[data-area]` (per Grep geprüft, keine einzige Regel dieser\nForm existiert). Die Bandfläche kommt in allen 22 Vorkommen als Inline-Style\nDIREKT am `.ep-cta-band`-Element (`style=\"background:var(--co-700);color:#fff\"`,\ndocs/index.html:6304 u. a.), dem Bereich der Page folgend, nie aus einem\n`data-area`-Attribut. Ein Element-Selektor würde exakt den in ADR-0008 „Fall 2“\ngemessenen Fehler wiederholen: der `<cds-cta-band>`-Host ist ein unbekanntes\nCustom Element (`display:inline`), sein einziges Kind würde als Block\nherausgebrochen, ein am Host gesetzter Inline-`background` hätte keine eigene Box\nzum Malen. Die Komponente hängt sich deshalb als Attribut an ein vom Konsumenten\ngeschriebenes `<div>` (`<div cdsCtaBand style=\"background:…\">`) — das Element, das\nden Style trägt, und `.ep-cta-band` sind derselbe Knoten. Nur `<div>`, kein\n`<section>`-Zwilling wie bei `cds-section`: alle 22 Vorkommen benutzen\nausnahmslos ein `<div>`, keines variiert das Tag.\n\n**Entscheidung 2 — `area` färbt nur die Aktion, nicht das Band.** Aus demselben\nGrund wie bei `cds-section` (siehe dessen Klassendoku, Entscheidung 2) bleibt die\nBandfläche Sache des Konsumenten: welche Bereichsfarbe ein Band trägt, ist eine\nEntscheidung der Seite (Bereichszugehörigkeit der Customer-Page), keine\nBauteil-Eigenschaft — und das CSS bietet an dieser Klasse ohnehin keinen\n`[data-area]`-Haken an (siehe Entscheidung 1). `area` steuert deshalb\nausschließlich die Button-Farbklasse (`.btn-{area}`), nicht die Füllung des Bands.\n\n**Entscheidung 3 — `.btn-on-band` + `.btn-filled` direkt komponiert, nicht über\n`cds-button` projiziert.** `cds-button` KENNT den Modifier\n(`variant=\"filled-on-band\"`, geprüft über `npx storybook tools docs show --id\nkomponenten-buttons-button` — die Story „Auf Bereichs-Band“ existiert dort\nbereits). Trotzdem baut diese Komponente die `.btn`-Klassen direkt zusammen\n(Präzedenzfall `download-cta.component.ts`), aus einem härteren Grund als dort:\n`cds-button` rendert IMMER ein `<button>` — sein Input-Vertrag\n(`ButtonComponentInputs`) kennt kein `href` — und kann die vom Ticket geforderte\nVerzweigung „`primaryHref` gesetzt → `<a>`“ strukturell gar nicht erfüllen. Ein\nprojiziertes `<cds-button>` brächte hier also kein Custom-Element-Layoutproblem\n(kein Kindselektor an `.ep-cta-band` hängt von der DOM-Tiefe ab), sondern schlicht\ndie falsche Fähigkeit.\n\n**Entscheidung 4 — bewusst nur EINE Aktion, kein `secondaryLabel`.** Eine frühere\nFassung bot `secondaryLabel`/`secondaryHref` an, ungeprüft gegen das Mockup. Beide\nBelege dagegen: erstens zeigt KEINES der 22 `.ep-cta-band`-Vorkommen in\n`docs/index.html` eine zweite Aktion — der Zwei-Aktionen-Fall hat keine Vorlage.\nZweitens (der schwerere Grund) lässt sich eine zweite, optisch zurückhaltendere\nAktion mit den vorhandenen Klassen gar nicht bauen: `.btn-{area}.btn-on-band{color:\n…}` (css/components.css:47–51) überschreibt die Textfarbe JEDER Variante auf den\nBereichston — genau den Ton, den die Doku durchgängig als Bandhintergrund nennt.\nNur `.btn-filled` tauscht zusätzlich die Füllung gegen Weiß; `.btn-outlined`,\n`.btn-tonal` und `.btn-text` blieben transparent und zeigten Text in derselben\nFarbe wie das Band darunter — unsichtbar. Eine zweite Aktion hätte deshalb\nzwangsläufig dieselbe Optik wie die erste getragen, keine Hierarchie, nur zwei\ngleich gewichtete Buttons. Das ist eine CSS-Lücke (kein kontrastsicherer\nsekundärer On-Band-Stil), festgehalten in\n`.scratch/angular-seitenbausteine/issues/16-css-luecke-zweite-aktion-auf-band.md`\n— gemeldet, nicht in diesem Wrapper improvisiert. Solange sie besteht, trägt\n`cds-cta-band` nur eine Aktion; ein Konsument mit echtem Bedarf für eine zweite\nschreibt sie außerhalb der Komponente ins Band hinein (`<ng-content>` gibt es\nhier bewusst nicht, um genau das nicht als unterstützten Pfad zu suggerieren).\nOhne zweite Aktion braucht es auch keinen eigenen Container: die einzelne Aktion\nzentriert sich über das ererbte `.ep-cta-band{text-align:center}` von selbst,\ngenau wie in allen 22 Mockup-Vorkommen — ein zusätzliches Flex-Layout im Wrapper\nhätte eine Darstellung erfunden, die die CSS-Schicht so nicht kennt (ADR-0001;\ndie einzige existierende Deklaration dieser Form ist `.ep-hero-ctas`,\ncss/components.css:1222, eine andere Klasse für einen anderen Bauteiltyp).\n\n**Kein `aria-label` auf der Aktion.** Anders als bei `cds-feature`\n(`ctaAriaLabel`, 11 von 12 Vorkommen mit `aria-label`) zeigt keines der 22\nBand-Vorkommen einen `aria-label` auf seiner Aktion — ausgezählt, nicht\ngeschätzt. Die sichtbaren Texte sind durchgängig konkrete Verben mit Ziel\n(„Termin buchen“, „Gespräch anfragen“, „Offene Stellen ansehen“ …) und stehen je\nSeite nur einmal; anders als bei einer Karten-Liste braucht es hier keine\nDisambiguierung. Kein entsprechender Input.",
386
+ "description": "CtaBand (`[cdsCtaBand]`) — Wrapper um `.ep-cta-band` aus css/components.css\n(css/components.css:1456, Kopf-Duo `.ep-cta-h2`/`.ep-cta-sub` ebenda): das\nbereichsgefärbte Page-End-CTA-Band, das laut Doku-Site (`docs/index.html:6237`,\nAnker `gt-cta-band`) am Ende jeder Customer-Page direkt vor dem Footer steht — die\n„letzte Einladung“, wenn die Nutzerin die Seite durchgelesen hat. Ausgezählt: 22\nVorkommen auf den Beispielseiten, ausnahmslos als `<div class=\"ep-cta-band\"\nstyle=\"background:…\">` mit genau einer Aktion.\n\n**Entscheidung 1 — Attributselektor, kein eigenes Element (ADR-0008, derselbe\n„Fläche am Host“-Fall wie `cds-section`).** `css/components.css` trägt für\n`.ep-cta-band` KEIN `[data-area]` (per Grep geprüft, keine einzige Regel dieser\nForm existiert). Die Bandfläche kommt in allen 22 Vorkommen als Inline-Style\nDIREKT am `.ep-cta-band`-Element (`style=\"background:var(--co-700);color:#fff\"`,\ndocs/index.html:6304 u. a.), dem Bereich der Page folgend, nie aus einem\n`data-area`-Attribut. Ein Element-Selektor würde exakt den in ADR-0008 „Fall 2“\ngemessenen Fehler wiederholen: der `<cds-cta-band>`-Host ist ein unbekanntes\nCustom Element (`display:inline`), sein einziges Kind würde als Block\nherausgebrochen, ein am Host gesetzter Inline-`background` hätte keine eigene Box\nzum Malen. Die Komponente hängt sich deshalb als Attribut an ein vom Konsumenten\ngeschriebenes `<div>` (`<div cdsCtaBand style=\"background:…\">`) — das Element, das\nden Style trägt, und `.ep-cta-band` sind derselbe Knoten. Nur `<div>`, kein\n`<section>`-Zwilling wie bei `cds-section`: alle 22 Vorkommen benutzen\nausnahmslos ein `<div>`, keines variiert das Tag.\n\n**Entscheidung 2 — `area` färbt nur die Aktion, nicht das Band.** Aus demselben\nGrund wie bei `cds-section` (siehe dessen Klassendoku, Entscheidung 2) bleibt die\nBandfläche Sache des Konsumenten: welche Bereichsfarbe ein Band trägt, ist eine\nEntscheidung der Seite (Bereichszugehörigkeit der Customer-Page), keine\nBauteil-Eigenschaft — und das CSS bietet an dieser Klasse ohnehin keinen\n`[data-area]`-Haken an (siehe Entscheidung 1). `area` steuert deshalb\nausschließlich die Button-Farbklasse (`.btn-{area}`), nicht die Füllung des Bands.\n\n**Entscheidung 3 — `.btn-on-band` + `.btn-filled` direkt komponiert, nicht über\n`cds-button` projiziert.** `cds-button` KENNT den Modifier\n(`variant=\"filled-on-band\"`, geprüft über `npx storybook tools docs show --id\nkomponenten-buttons-button` — die Story „Auf Bereichs-Band“ existiert dort\nbereits). Trotzdem baut diese Komponente die `.btn`-Klassen direkt zusammen\n(Präzedenzfall `download-cta.component.ts`), aus einem härteren Grund als dort:\n`cds-button` rendert IMMER ein `<button>` — sein Input-Vertrag\n(`ButtonComponentInputs`) kennt kein `href` — und kann die hier nötige\nVerzweigung „`primaryHref` gesetzt → `<a>`“ strukturell gar nicht erfüllen. Ein\nprojiziertes `<cds-button>` brächte hier also kein Custom-Element-Layoutproblem\n(kein Kindselektor an `.ep-cta-band` hängt von der DOM-Tiefe ab), sondern schlicht\ndie falsche Fähigkeit.\n\n**Entscheidung 4 — bewusst nur EINE Aktion, kein `secondaryLabel`.** Eine frühere\nFassung bot `secondaryLabel`/`secondaryHref` an, ungeprüft gegen das Mockup. Beide\nBelege dagegen: erstens zeigt KEINES der 22 `.ep-cta-band`-Vorkommen in\n`docs/index.html` eine zweite Aktion — der Zwei-Aktionen-Fall hat keine Vorlage.\nZweitens (der schwerere Grund) lässt sich eine zweite, optisch zurückhaltendere\nAktion mit den vorhandenen Klassen gar nicht bauen: `.btn-{area}.btn-on-band{color:\n…}` (css/components.css:47–51) überschreibt die Textfarbe JEDER Variante auf den\nBereichston — genau den Ton, den die Doku durchgängig als Bandhintergrund nennt.\nNur `.btn-filled` tauscht zusätzlich die Füllung gegen Weiß; `.btn-outlined`,\n`.btn-tonal` und `.btn-text` blieben transparent und zeigten Text in derselben\nFarbe wie das Band darunter — unsichtbar. Eine zweite Aktion hätte deshalb\nzwangsläufig dieselbe Optik wie die erste getragen, keine Hierarchie, nur zwei\ngleich gewichtete Buttons. Das ist eine CSS-Lücke (kein kontrastsicherer\nsekundärer On-Band-Stil) — gemeldet, nicht in diesem Wrapper improvisiert. Solange sie besteht, trägt\n`cds-cta-band` nur eine Aktion; ein Konsument mit echtem Bedarf für eine zweite\nschreibt sie außerhalb der Komponente ins Band hinein (`<ng-content>` gibt es\nhier bewusst nicht, um genau das nicht als unterstützten Pfad zu suggerieren).\nOhne zweite Aktion braucht es auch keinen eigenen Container: die einzelne Aktion\nzentriert sich über das ererbte `.ep-cta-band{text-align:center}` von selbst,\ngenau wie in allen 22 Mockup-Vorkommen — ein zusätzliches Flex-Layout im Wrapper\nhätte eine Darstellung erfunden, die die CSS-Schicht so nicht kennt (ADR-0001;\ndie einzige existierende Deklaration dieser Form ist `.ep-hero-ctas`,\ncss/components.css:1222, eine andere Klasse für einen anderen Bauteiltyp).\n\n**Kein `aria-label` auf der Aktion.** Anders als bei `cds-feature`\n(`ctaAriaLabel`, 11 von 12 Vorkommen mit `aria-label`) zeigt keines der 22\nBand-Vorkommen einen `aria-label` auf seiner Aktion — ausgezählt, nicht\ngeschätzt. Die sichtbaren Texte sind durchgängig konkrete Verben mit Ziel\n(„Termin buchen“, „Gespräch anfragen“, „Offene Stellen ansehen“ …) und stehen je\nSeite nur einmal; anders als bei einer Karten-Liste braucht es hier keine\nDisambiguierung. Kein entsprechender Input.",
387
387
  "docgen": {
388
388
  "$ref": "../services/core/docgen/komponenten-call-to-action-cta-band.json#/components/komponenten-call-to-action-cta-band"
389
389
  },
@@ -415,7 +415,7 @@
415
415
  "komponenten-tabelle-tabelle": {
416
416
  "id": "komponenten-tabelle-tabelle",
417
417
  "name": "TableComponent",
418
- "description": "Table (`cds-table`) — Wrapper um `.tbl`/`.tbl-wrap` aus css/components.css\n(css/components.css:1462–1480): die Datentabelle der Doku-Site (`sec-table`,\nNav-Einträge „Standard“ und „Gestreift“). Ausgezählt: außerhalb von `sec-table`\nselbst kommt `.tbl-wrap` nur zweimal vor, beide auf Beitragsseiten\n(`docs/index.html:8622`, `15080`) — keine der 24 Beispielseiten außerhalb von\nWissensbeiträgen setzt aktuell eine Tabelle ein. Neuntes Ticket der\nSeitenbausteine-Serie.\n\n**Kein Daten-Input.** Eine `columns`/`rows`-API würde Zellinhalte auf Strings\nfestlegen; die Beispielseiten setzen darin Badges, Links und `data-num`\n(`docs/index.html:5076–5177`). Der Konsument projiziert `<thead>`/`<tbody>`/\n`<tfoot>` unverändert per `<ng-content>`, die Komponente liefert nur die\nHülle: `.tbl-wrap`, `.tbl`, `<caption>` und die Striped-Klasse.\n\n**`caption` ist Pflicht (`input.required<string>()`), abweichend von der\nTicket-Skizze (dort `caption? = ''`).** Die eigene Doku dieser Komponente\n(`storybook-angular/src/docs/komponenten/tabelle.mdx:137`) nennt `<caption>`\nausdrücklich „Pflicht, Screenreader lesen den Titel vor, bevor die Zellen\nvorgelesen werden“ und wiederholt das in den Dos (`tabelle.mdx:147`:\n„`<caption>` und `scope`-Attribute immer setzen, auch bei einfachen\nTabellen“). Ausgezählt: alle 5 realen `.tbl`-Vorkommen in `docs/index.html`\nhaben eine `<caption>`, ausnahmslos. Ein optionaler Input mit Default `''`\nhätte genau den Fall erlaubt, den die eigene Doku verbietet — nach\nADR-0007 §2 ist `caption` damit Inhalt, nicht Beiwerk: ohne sie ist eine\n`cds-table` eine Tabelle, die die eigenen Barrierefreiheitsregeln bricht,\nalso sinnlos. Ein vergessenes Binding wirft jetzt `NG0950`, statt eine\nunbeschriftete Tabelle still auszuliefern.\n\n**Element-Selektor, `.tbl-wrap`/`.tbl` sitzen im eigenen Template, nicht am\nHost (ADR-0008-Standardfall, analog zu `cds-facts`/`cds-faq`).** Ausgezählt:\nalle 5 `.tbl-wrap`-Vorkommen in `docs/index.html` (Zeilen 5061, 5116, 5236,\n8622, 15080). Keines ist selbst ein direktes Grid-/Flex-Kind — das einzige\nVorkommen in einem `.layout-grid` (Zeile 5236) sitzt eine Ebene tiefer in\neinem `.col-8`, das seinerseits der Grid-Child ist; `.tbl-wrap` ist dort ein\ngewöhnlicher Block-Nachfahre, dessen Breite vom umschließenden `.col-8`\nkommt, nicht von `align-items:stretch` auf einer bestimmten DOM-Tiefe (kein\n`.ep-card`-Fall). Keines trägt eine `col-*`-Klasse selbst, keines variiert\ndas Tag (immer `<div class=\"tbl-wrap\">`). Ein `<cds-table>`-Host ohne eigene\nKlasse und ohne eigenes `background` gerät deshalb nicht in den „Fläche am\nHost“-Fall aus ADR-0008 Fall 2: die Komponente setzt nichts auf dem Host,\ndas eine eigene Box bräuchte, `.tbl-wrap` bleibt ein normaler Block-Nachfahre\nunabhängig vom `display` des `<cds-table>`-Hosts (derselbe Grund, warum\n`cds-facts`/`cds-faq` als Element-Selektor funktionieren).\n\nEine Attributselektor-Variante direkt am `<table>` (`table[cdsTable]`, analog\nzu `cdsSection`) wurde erwogen und verworfen: `<table>` ist als Tag ohnehin\nfix (kein Tag-Wechsel wie bei `cdsIconCard`), aber `.tbl-wrap` bräuchte dann\nein ZWEITES Element, das der Konsument von Hand um die Tabelle schreiben\nmüsste (`<div class=\"tbl-wrap\" tabindex=\"0\" role=\"region\" aria-label=\"…\">\n<table cdsTable>…`) — genau die beiden Aufgaben, die laut Ticket am ehesten\nvergessen werden (fokussierbarer, benannter Scroll-Container), blieben dann\nbeim Konsumenten statt bei der Komponente. Der Element-Selektor hält Wrap und\nTabelle zusammen in einem Template.\n\n**Geprüft im laufenden Storybook (Story „Interaktiv“, Play-Funktion):**\n`<table class=\"tbl\">` liegt direkt unter `<div class=\"tbl-wrap\">`, `<caption>`\nist ihr erstes Kind, danach folgen ohne zusätzlichen Knoten dazwischen genau\ndie projizierten `<thead>`/`<tbody>`-Elemente — `<ng-content>` fügt selbst\nkein DOM-Element ein, es verschiebt nur die vom Konsumenten geschriebenen\nnativen Elemente an ihre Stelle. Damit bleibt die Tabellenstruktur (`<table>`\n→ `<caption>` + `<thead>` + `<tbody>` + optional `<tfoot>`, keine fremde\nEbene dazwischen) exakt so gültig wie im rohen Markup.\n\n**`.tbl-wrap` ist immer fokussierbar UND immer benannt.** Das CSS gibt dem\nScroll-Container einen eigenen `:focus-visible`-Ring\n(css/components.css:1463) — ein scrollbarer Bereich muss per Tastatur\nerreichbar sein (WCAG 2.1.1), `tabindex=\"0\"` steht deshalb fest im Template.\n`role=\"region\"` braucht laut HTML-AAM einen zugänglichen Namen, sonst bekäme\nder Bereich keine Landmark-Rolle — weil `caption` jetzt Pflicht ist, kann\n`accessibleName()` (`scrollLabel() || caption()`) nicht mehr leer werden,\n`role`/`aria-label` sind deshalb unbedingt gesetzt, kein Nullfall mehr zu\nbehandeln. `scrollLabel` bleibt eine optionale Überschreibung (Vorrang vor\n`caption`) für Fälle, in denen der sichtbare Tabellentitel für den\nScroll-Container zu lang oder zu unspezifisch wäre.\n\n**`.tbl-sort` bleibt außen vor.** Die Klasse existiert\n(css/components.css:1477–1482), die Sortierlogik nicht — kein zugehöriges\nJS in `docs/main.js` für die Beispielseiten. Ein Wrapper, der nur den\nButton-Look lieferte, täuschte Funktion vor, die es nicht gibt. Siehe\n`.scratch/angular-seitenbausteine/issues/19-fehlende-sortierlogik-tbl-sort.md`.",
418
+ "description": "Table (`cds-table`) — Wrapper um `.tbl`/`.tbl-wrap` aus css/components.css\n(css/components.css:1462–1480): die Datentabelle der Doku-Site (`sec-table`,\nNav-Einträge „Standard“ und „Gestreift“). Ausgezählt: außerhalb von `sec-table`\nselbst kommt `.tbl-wrap` nur zweimal vor, beide auf Beitragsseiten\n(`docs/index.html:8622`, `15080`) — keine der 24 Beispielseiten außerhalb von\nWissensbeiträgen setzt aktuell eine Tabelle ein.\n\n**Kein Daten-Input.** Eine `columns`/`rows`-API würde Zellinhalte auf Strings\nfestlegen; die Beispielseiten setzen darin Badges, Links und `data-num`\n(`docs/index.html:5076–5177`). Der Konsument projiziert `<thead>`/`<tbody>`/\n`<tfoot>` unverändert per `<ng-content>`, die Komponente liefert nur die\nHülle: `.tbl-wrap`, `.tbl`, `<caption>` und die Striped-Klasse.\n\n**`caption` ist Pflicht (`input.required<string>()`), abweichend von der\nursprünglichen Skizze (dort `caption? = ''`).** Die eigene Doku dieser Komponente\n(`storybook-angular/src/docs/komponenten/tabelle.mdx:137`) nennt `<caption>`\nausdrücklich „Pflicht, Screenreader lesen den Titel vor, bevor die Zellen\nvorgelesen werden“ und wiederholt das in den Dos (`tabelle.mdx:147`:\n„`<caption>` und `scope`-Attribute immer setzen, auch bei einfachen\nTabellen“). Ausgezählt: alle 5 realen `.tbl`-Vorkommen in `docs/index.html`\nhaben eine `<caption>`, ausnahmslos. Ein optionaler Input mit Default `''`\nhätte genau den Fall erlaubt, den die eigene Doku verbietet — nach\nADR-0007 §2 ist `caption` damit Inhalt, nicht Beiwerk: ohne sie ist eine\n`cds-table` eine Tabelle, die die eigenen Barrierefreiheitsregeln bricht,\nalso sinnlos. Ein vergessenes Binding wirft jetzt `NG0950`, statt eine\nunbeschriftete Tabelle still auszuliefern.\n\n**Element-Selektor, `.tbl-wrap`/`.tbl` sitzen im eigenen Template, nicht am\nHost (ADR-0008-Standardfall, analog zu `cds-facts`/`cds-faq`).** Ausgezählt:\nalle 5 `.tbl-wrap`-Vorkommen in `docs/index.html` (Zeilen 5061, 5116, 5236,\n8622, 15080). Keines ist selbst ein direktes Grid-/Flex-Kind — das einzige\nVorkommen in einem `.layout-grid` (Zeile 5236) sitzt eine Ebene tiefer in\neinem `.col-8`, das seinerseits der Grid-Child ist; `.tbl-wrap` ist dort ein\ngewöhnlicher Block-Nachfahre, dessen Breite vom umschließenden `.col-8`\nkommt, nicht von `align-items:stretch` auf einer bestimmten DOM-Tiefe (kein\n`.ep-card`-Fall). Keines trägt eine `col-*`-Klasse selbst, keines variiert\ndas Tag (immer `<div class=\"tbl-wrap\">`). Ein `<cds-table>`-Host ohne eigene\nKlasse und ohne eigenes `background` gerät deshalb nicht in den „Fläche am\nHost“-Fall aus ADR-0008 Fall 2: die Komponente setzt nichts auf dem Host,\ndas eine eigene Box bräuchte, `.tbl-wrap` bleibt ein normaler Block-Nachfahre\nunabhängig vom `display` des `<cds-table>`-Hosts (derselbe Grund, warum\n`cds-facts`/`cds-faq` als Element-Selektor funktionieren).\n\nEine Attributselektor-Variante direkt am `<table>` (`table[cdsTable]`, analog\nzu `cdsSection`) wurde erwogen und verworfen: `<table>` ist als Tag ohnehin\nfix (kein Tag-Wechsel wie bei `cdsIconCard`), aber `.tbl-wrap` bräuchte dann\nein ZWEITES Element, das der Konsument von Hand um die Tabelle schreiben\nmüsste (`<div class=\"tbl-wrap\" tabindex=\"0\" role=\"region\" aria-label=\"…\">\n<table cdsTable>…`) — genau die beiden Aufgaben, die am ehesten\nvergessen werden (fokussierbarer, benannter Scroll-Container), blieben dann\nbeim Konsumenten statt bei der Komponente. Der Element-Selektor hält Wrap und\nTabelle zusammen in einem Template.\n\n**Geprüft im laufenden Storybook (Story „Interaktiv“, Play-Funktion):**\n`<table class=\"tbl\">` liegt direkt unter `<div class=\"tbl-wrap\">`, `<caption>`\nist ihr erstes Kind, danach folgen ohne zusätzlichen Knoten dazwischen genau\ndie projizierten `<thead>`/`<tbody>`-Elemente — `<ng-content>` fügt selbst\nkein DOM-Element ein, es verschiebt nur die vom Konsumenten geschriebenen\nnativen Elemente an ihre Stelle. Damit bleibt die Tabellenstruktur (`<table>`\n→ `<caption>` + `<thead>` + `<tbody>` + optional `<tfoot>`, keine fremde\nEbene dazwischen) exakt so gültig wie im rohen Markup.\n\n**`.tbl-wrap` ist immer fokussierbar UND immer benannt.** Das CSS gibt dem\nScroll-Container einen eigenen `:focus-visible`-Ring\n(css/components.css:1463) — ein scrollbarer Bereich muss per Tastatur\nerreichbar sein (WCAG 2.1.1), `tabindex=\"0\"` steht deshalb fest im Template.\n`role=\"region\"` braucht laut HTML-AAM einen zugänglichen Namen, sonst bekäme\nder Bereich keine Landmark-Rolle — weil `caption` jetzt Pflicht ist, kann\n`accessibleName()` (`scrollLabel() || caption()`) nicht mehr leer werden,\n`role`/`aria-label` sind deshalb unbedingt gesetzt, kein Nullfall mehr zu\nbehandeln. `scrollLabel` bleibt eine optionale Überschreibung (Vorrang vor\n`caption`) für Fälle, in denen der sichtbare Tabellentitel für den\nScroll-Container zu lang oder zu unspezifisch wäre.\n\n**`.tbl-sort` bleibt außen vor.** Die Klasse existiert\n(css/components.css:1477–1482), die Sortierlogik nicht — kein zugehöriges\nJS in `docs/main.js` für die Beispielseiten. Ein Wrapper, der nur den\nButton-Look lieferte, täuschte Funktion vor, die es nicht gibt.",
419
419
  "docgen": {
420
420
  "$ref": "../services/core/docgen/komponenten-tabelle-tabelle.json#/components/komponenten-tabelle-tabelle"
421
421
  },
@@ -436,7 +436,7 @@
436
436
  "komponenten-tabelle-vergleichstabelle": {
437
437
  "id": "komponenten-tabelle-vergleichstabelle",
438
438
  "name": "CompareComponent",
439
- "description": "Compare (`cds-compare`) — Wrapper um `.ep-compare*` aus css/components.css\n(css/components.css:1275–1294): die aufklappbare Vergleichstabelle für den zeilenweisen\nDirektvergleich mehrerer Pakete/Tarife (Doku-Site `sec-table`, Nav-Eintrag\n„Vergleichstabelle (aufklappbar)“, `docs/index.html:5182–5204`). Zehntes Ticket der\nSeitenbausteine-Serie, nach `cds-table` (Ticket 09).\n\n**Mit Daten-Input, anders als `cds-table`.** `cds-table` projiziert `<thead>`/`<tbody>`\nunverändert per `<ng-content>`, weil seine Zellen Badges, Links und `data-num` tragen können\n(siehe dessen Klassendoku). Hier ist der Zellinhalt strukturell auf Ja/Nein oder eine kurze\nAngabe begrenzt — ausgezählt: alle 22 Datenzellen des einzigen realen Vorkommens\n(`docs/index.html:11970–12038`, 11 Zeilen × 2 Spalten, ohne die Zeilenüberschriften) sind\nentweder ein `.ep-compare-yes`/`-no`-Marker (18×, davon 14× „Ja“, 4× „Nein“) oder ein kurzer\nText wie „bis 70 Mrd. Param.“ bzw. „30 Min.“ (4×) — nie ein Badge, Link oder anderes Markup.\nWeil Spaltenzahl, `pro`-Spalte und Zellinhalt damit vollständig aus zwei Arrays (`columns`,\n`rows`) hervorgehen, würde `<ng-content>` beim Konsumenten nur denselben `@for`-Code\nduplizieren, ohne eine Freiheit zu gewinnen, die die Zell-Typisierung (`boolean | string`)\nohnehin ausschließt.\n\n**Ja/Nein-Zellen tragen einen Screenreader-Text, übernommen statt erfunden.** Im einzigen\nrealen Vorkommen steht hinter jeder Glyphe ein `.sr-only`-Text, ausnahmslos:\n`<span class=\"ep-compare-yes\" aria-hidden=\"true\">✓</span><span class=\"sr-only\">Enthalten</span>`\n(14×) bzw. `<span class=\"ep-compare-no\" aria-hidden=\"true\">−</span><span class=\"sr-only\">Nicht\nenthalten</span>` (4×, „−“ ist U+2212 Minus, kein Bindestrich). Genau diese Zeichen und dieser\nWortlaut, keine eigene Formulierung — Farbe und Glyphe allein tragen keine Bedeutung\n(WCAG 1.4.1), der `.sr-only`-Text ist deshalb kein optionaler Zusatz, sondern fest verdrahtet,\nnicht per Input überschreibbar.\n\n**`caption` ist Pflicht (`input.required<string>()`), abweichend von der Ticket-Skizze (dort\nkein Input dafür vorgesehen) — dieselbe Begründung wie bei `cds-table`.** Die eigene Doku\n(`storybook-angular/src/docs/komponenten/tabelle.mdx:91`) nennt `<caption class=\"sr-only\">`\nausdrücklich als Teil des Bauteils, und das einzige reale Vorkommen hat eine. Anders als bei\n`cds-table` ist sie hier `.sr-only`, nicht sichtbar: der Klartext steht schon im `<summary>`\n(`summary`-Input), die `<caption>` wiederholt ihn nur für Screenreader, die nach dem Aufklappen\ndirekt in die Tabelle springen, ohne den Summary-Text erneut vorgelesen zu bekommen.\n\n**`rowsLabel` (erste Kopfzelle, `<th scope=\"col\">Funktion</th>` im Mockup) ist dagegen Beiwerk,\noptional mit Default `''`.** Auch dafür sieht die Ticket-Skizze keinen Input vor. Den Text fest\nim Template zu verdrahten hieße, AI.Box-Copy in einen wiederverwendbaren Wrapper zu backen\n(ADR-0007 §2); ihn ganz wegzulassen wich ohne CSS-Befund vom einzigen Beleg ab, ohne dass die\nDoku das verlangt. Anders als `caption` ist die Kopfzelle nicht barrierefreiheitsrelevant\nPflicht: ein leeres `<th scope=\"col\">` bleibt eine gültige, in Vergleichsmatrizen verbreitete\nEcke ohne eigenen Namen. Deshalb Beiwerk, kein Inhalt — das Beispielwort „Funktion“ steht in\nder Story, nicht in der Klasse.\n\n**Natives `<details>`/`<summary>` bleibt erhalten, kein nachgebautes Disclosure** (Ticket-\nVorgabe, analog `cds-faq`): Tastaturbedienung und Toggle-Verhalten kommen vom Browser, das CSS\nhängt direkt an `[open]` (`.ep-compare[open] .ep-compare-caret`). `<details class=\"ep-compare\">`\nsitzt deshalb im TEMPLATE, nicht am Host — derselbe Grund wie bei `cds-table`s\n`.tbl-wrap`/`.tbl`: ein `<cds-compare>`-Host ist ein unbekanntes Custom Element, kein echtes\n`<details>`, und kann dessen native Disclosure-Semantik nicht annehmen.\n\n**Element-Selektor (ADR-0008-Standardfall).** `.ep-compare` sitzt im einzigen realen Vorkommen\nals gewöhnlicher Block-Nachfahre in `.ep-section` (`docs/index.html:11969`) — kein Grid-/\nFlex-Kind, keine `col-*`-Klasse vom Konsumenten, kein Tag-Wechsel (immer `<details>`). Keines\nder drei ADR-0008-Kriterien greift, ein Attributselektor brächte hier nichts.\n\n**`open` ist ein reiner Anfangszustand, keine Zwei-Wege-Bindung.** Gebunden über\n`[attr.open]=\"open() ? '' : null\"`: Angular schreibt das Attribut nur, wenn sich der GEPRÜFTE\nAusdruckswert seit dem letzten Change-Detection-Lauf ändert. `open()` bleibt nach dem ersten\nRendern konstant (die Komponente schreibt nie in dieses Signal zurück), ein nachfolgender\nnativer Toggle wird deshalb nicht rückgängig gemacht — geprüft in der Story „Interaktiv“\n(Play-Funktion: Klick öffnet, `toggled` feuert `true`, ein zweiter, vom Toggle unabhängiger\nInteraktionsschritt lässt die Tabelle weiterhin offen).\n\nVerwendungsguidance dieser Gruppe: siehe Tabelle (`komponenten-tabelle-tabelle--verwendung`).",
439
+ "description": "Compare (`cds-compare`) — Wrapper um `.ep-compare*` aus css/components.css\n(css/components.css:1275–1294): die aufklappbare Vergleichstabelle für den zeilenweisen\nDirektvergleich mehrerer Pakete/Tarife (Doku-Site `sec-table`, Nav-Eintrag\n„Vergleichstabelle (aufklappbar)“, `docs/index.html:5182–5204`), nach `cds-table`.\n\n**Mit Daten-Input, anders als `cds-table`.** `cds-table` projiziert `<thead>`/`<tbody>`\nunverändert per `<ng-content>`, weil seine Zellen Badges, Links und `data-num` tragen können\n(siehe dessen Klassendoku). Hier ist der Zellinhalt strukturell auf Ja/Nein oder eine kurze\nAngabe begrenzt — ausgezählt: alle 22 Datenzellen des einzigen realen Vorkommens\n(`docs/index.html:11970–12038`, 11 Zeilen × 2 Spalten, ohne die Zeilenüberschriften) sind\nentweder ein `.ep-compare-yes`/`-no`-Marker (18×, davon 14× „Ja“, 4× „Nein“) oder ein kurzer\nText wie „bis 70 Mrd. Param.“ bzw. „30 Min.“ (4×) — nie ein Badge, Link oder anderes Markup.\nWeil Spaltenzahl, `pro`-Spalte und Zellinhalt damit vollständig aus zwei Arrays (`columns`,\n`rows`) hervorgehen, würde `<ng-content>` beim Konsumenten nur denselben `@for`-Code\nduplizieren, ohne eine Freiheit zu gewinnen, die die Zell-Typisierung (`boolean | string`)\nohnehin ausschließt.\n\n**Ja/Nein-Zellen tragen einen Screenreader-Text, übernommen statt erfunden.** Im einzigen\nrealen Vorkommen steht hinter jeder Glyphe ein `.sr-only`-Text, ausnahmslos:\n`<span class=\"ep-compare-yes\" aria-hidden=\"true\">✓</span><span class=\"sr-only\">Enthalten</span>`\n(14×) bzw. `<span class=\"ep-compare-no\" aria-hidden=\"true\">−</span><span class=\"sr-only\">Nicht\nenthalten</span>` (4×, „−“ ist U+2212 Minus, kein Bindestrich). Genau diese Zeichen und dieser\nWortlaut, keine eigene Formulierung — Farbe und Glyphe allein tragen keine Bedeutung\n(WCAG 1.4.1), der `.sr-only`-Text ist deshalb kein optionaler Zusatz, sondern fest verdrahtet,\nnicht per Input überschreibbar.\n\n**`caption` ist Pflicht (`input.required<string>()`), abweichend von der ursprünglichen Skizze (dort\nkein Input dafür vorgesehen) — dieselbe Begründung wie bei `cds-table`.** Die eigene Doku\n(`storybook-angular/src/docs/komponenten/tabelle.mdx:91`) nennt `<caption class=\"sr-only\">`\nausdrücklich als Teil des Bauteils, und das einzige reale Vorkommen hat eine. Anders als bei\n`cds-table` ist sie hier `.sr-only`, nicht sichtbar: der Klartext steht schon im `<summary>`\n(`summary`-Input), die `<caption>` wiederholt ihn nur für Screenreader, die nach dem Aufklappen\ndirekt in die Tabelle springen, ohne den Summary-Text erneut vorgelesen zu bekommen.\n\n**`rowsLabel` (erste Kopfzelle, `<th scope=\"col\">Funktion</th>` im Mockup) ist dagegen Beiwerk,\noptional mit Default `''`.** Auch dafür sieht die ursprüngliche Skizze keinen Input vor. Den Text fest\nim Template zu verdrahten hieße, AI.Box-Copy in einen wiederverwendbaren Wrapper zu backen\n(ADR-0007 §2); ihn ganz wegzulassen wich ohne CSS-Befund vom einzigen Beleg ab, ohne dass die\nDoku das verlangt. Anders als `caption` ist die Kopfzelle nicht barrierefreiheitsrelevant\nPflicht: ein leeres `<th scope=\"col\">` bleibt eine gültige, in Vergleichsmatrizen verbreitete\nEcke ohne eigenen Namen. Deshalb Beiwerk, kein Inhalt — das Beispielwort „Funktion“ steht in\nder Story, nicht in der Klasse.\n\n**Natives `<details>`/`<summary>` bleibt erhalten, kein nachgebautes Disclosure**\n(analog `cds-faq`): Tastaturbedienung und Toggle-Verhalten kommen vom Browser, das CSS\nhängt direkt an `[open]` (`.ep-compare[open] .ep-compare-caret`). `<details class=\"ep-compare\">`\nsitzt deshalb im TEMPLATE, nicht am Host — derselbe Grund wie bei `cds-table`s\n`.tbl-wrap`/`.tbl`: ein `<cds-compare>`-Host ist ein unbekanntes Custom Element, kein echtes\n`<details>`, und kann dessen native Disclosure-Semantik nicht annehmen.\n\n**Element-Selektor (ADR-0008-Standardfall).** `.ep-compare` sitzt im einzigen realen Vorkommen\nals gewöhnlicher Block-Nachfahre in `.ep-section` (`docs/index.html:11969`) — kein Grid-/\nFlex-Kind, keine `col-*`-Klasse vom Konsumenten, kein Tag-Wechsel (immer `<details>`). Keines\nder drei ADR-0008-Kriterien greift, ein Attributselektor brächte hier nichts.\n\n**`open` ist ein reiner Anfangszustand, keine Zwei-Wege-Bindung.** Gebunden über\n`[attr.open]=\"open() ? '' : null\"`: Angular schreibt das Attribut nur, wenn sich der GEPRÜFTE\nAusdruckswert seit dem letzten Change-Detection-Lauf ändert. `open()` bleibt nach dem ersten\nRendern konstant (die Komponente schreibt nie in dieses Signal zurück), ein nachfolgender\nnativer Toggle wird deshalb nicht rückgängig gemacht — geprüft in der Story „Interaktiv“\n(Play-Funktion: Klick öffnet, `toggled` feuert `true`, ein zweiter, vom Toggle unabhängiger\nInteraktionsschritt lässt die Tabelle weiterhin offen).\n\nVerwendungsguidance dieser Gruppe: siehe Tabelle (`komponenten-tabelle-tabelle--verwendung`).",
440
440
  "docgen": {
441
441
  "$ref": "../services/core/docgen/komponenten-tabelle-vergleichstabelle.json#/components/komponenten-tabelle-vergleichstabelle"
442
442
  },
@@ -585,7 +585,7 @@
585
585
  "komponenten-hero-hero-bild": {
586
586
  "id": "komponenten-hero-hero-bild",
587
587
  "name": "HeroImageComponent",
588
- "description": "HeroImage (cds-hero-image) — Wrapper um `.hero-image` aus css/components.css\n(css/components.css:851–878) mit optionaler Caption als Gradient-Overlay\n(`-caption`, `-caption-eyebrow`, `-caption-title`, `-caption-text`). Vollbreites,\nrandloses `<figure>` im 21:9-Format, der Standard-Hero auf allen Customer-Pages\n(17 Vorkommen in den Beispielseiten). Semantisch ein figure/figcaption-Pattern:\n`alt` trägt die faktische Bildbeschreibung, die Caption die redaktionelle\nEinordnung (Eyebrow, Titel, Text) — beide sollen sich nach `docs/index.html`\n(Abschnitt „In-Article Figure“, dasselbe Prinzip) nicht inhaltlich decken.\n\n**Entscheidung 1 — kein `<figcaption>` ohne Textinhalt.** Bleiben `eyebrow`,\n`heading` und `text` alle leer, entfällt das `<figcaption>`-Element vollständig\nstatt leer zu rendern (Variante „Hero ohne Caption“, siehe\n`storybook-angular/src/docs/seitenmuster/wissensbeitrag.mdx` Abschnitt „Aufbau“,\nZeile 3: Beitrags-Heros zeigen das visuelle Versprechen ohne Marketing-Overlay).\nEin leeres `<figcaption>` wäre totes Markup ohne Zugänglichkeitsnutzen.\n\n**Entscheidung 2 — `headingLevel` statt fest verdrahtetem `<h1>`.** Das Mockup\n(`docs/index.html:9412` u. a.) rendert `.hero-image-caption-title` durchgehend als\n`<h1>`, weil der Hero dort das erste Überschriften-Element der Seite ist. Sitzt\nder Hero dagegen unter einem eigenen `<h1>` — der Wissensbeitrag hat seinen\nTitel bereits im Article-Header (`.article-title`, ebenfalls `<h1>`) —, entstünde\nmit einem zweiten `<h1>` eine doppelte Top-Überschrift. `headingLevel` (Default\n`1`) macht die Ebene explizit, `.hero-image-caption-title` ist reine CSS-Klasse\nund trägt keine Heading-Semantik, das Styling bleibt in beiden Fällen identisch.\n\n**Entscheidung 3 — `objectPosition` als Style-Binding, die eine Ausnahme von\nSpec-Randbedingung 6.** Der Bildausschnitt ist eine Eigenschaft des konkreten\nBildes (Motiv, Kopfposition), nicht der Seite — anders als z. B. eine\nSektionsfläche, die vom Seitenkontext abhängt. `docs/index.html` setzt ihn\ndeshalb an jedem Hero individuell direkt am `<img>` (z. B. `object-position:center\n70%`, Zeile 2640). Ohne diesen Input wäre die Komponente für Porträt-lastige\nMotive unbrauchbar: `object-fit:cover` beschneidet das Bild sobald der\n`max-height`-Cap greift (siehe `.hero-image-media`), und ohne Steuerung landet\nder Ausschnitt zufällig in der Bildmitte. Als Style-Binding am `<img>`, nicht als\nKlasse — es gibt in der CSS-Schicht keine Modifier-Klassen dafür, nur den\nInline-Weg, den das Mockup selbst nutzt.\n\n**Entscheidung 4 — `tabindex`/`id` sind nicht Teil der Komponente.** Das Mockup\nsetzt beides am `<figure>` (`id=\"ep-landing-main\" tabindex=\"-1\"`, z. B. Zeile\n9406) als Sprungziel des Skip-Links. Das Sprungziel ist eine Entscheidung der\nSeite (welches Element „Hauptinhalt“ ist, hängt vom Seitenaufbau ab, nicht vom\nHero selbst), deshalb setzt der Konsument beides am `<cds-hero-image>`-Host.\nDamit dieses Sprungziel eine eigene Box hat, ist der Host per\n`:host{display:block}` selbst ein Block (Muster wie `cds-footer-main`): Als\nunbekanntes Element wäre er `display:inline` mit herausgebrochenem Block-Kind\nund hätte keine brauchbare Box für Fokus und Scroll-Position (ADR-0008, Fall 2).",
588
+ "description": "HeroImage (cds-hero-image) — Wrapper um `.hero-image` aus css/components.css\n(css/components.css:851–878) mit optionaler Caption als Gradient-Overlay\n(`-caption`, `-caption-eyebrow`, `-caption-title`, `-caption-text`). Vollbreites,\nrandloses `<figure>` im 21:9-Format, der Standard-Hero auf allen Customer-Pages\n(17 Vorkommen in den Beispielseiten). Semantisch ein figure/figcaption-Pattern:\n`alt` trägt die faktische Bildbeschreibung, die Caption die redaktionelle\nEinordnung (Eyebrow, Titel, Text) — beide sollen sich nach `docs/index.html`\n(Abschnitt „In-Article Figure“, dasselbe Prinzip) nicht inhaltlich decken.\n\n**Entscheidung 1 — kein `<figcaption>` ohne Textinhalt.** Bleiben `eyebrow`,\n`heading` und `text` alle leer, entfällt das `<figcaption>`-Element vollständig\nstatt leer zu rendern (Variante „Hero ohne Caption“, siehe\n`storybook-angular/src/docs/seitenmuster/wissensbeitrag.mdx` Abschnitt „Aufbau“,\nZeile 3: Beitrags-Heros zeigen das visuelle Versprechen ohne Marketing-Overlay).\nEin leeres `<figcaption>` wäre totes Markup ohne Zugänglichkeitsnutzen.\n\n**Entscheidung 2 — `headingLevel` statt fest verdrahtetem `<h1>`.** Das Mockup\n(`docs/index.html:9412` u. a.) rendert `.hero-image-caption-title` durchgehend als\n`<h1>`, weil der Hero dort das erste Überschriften-Element der Seite ist. Sitzt\nder Hero dagegen unter einem eigenen `<h1>` — der Wissensbeitrag hat seinen\nTitel bereits im Article-Header (`.article-title`, ebenfalls `<h1>`) —, entstünde\nmit einem zweiten `<h1>` eine doppelte Top-Überschrift. `headingLevel` (Default\n`1`) macht die Ebene explizit, `.hero-image-caption-title` ist reine CSS-Klasse\nund trägt keine Heading-Semantik, das Styling bleibt in beiden Fällen identisch.\n\n**Entscheidung 3 — `objectPosition` als Style-Binding, bewusste Ausnahme von\nder Regel, dass Mockup-Inline-Styles nicht in den Wrapper wandern.** Der\nBildausschnitt ist eine Eigenschaft des konkreten\nBildes (Motiv, Kopfposition), nicht der Seite — anders als z. B. eine\nSektionsfläche, die vom Seitenkontext abhängt. `docs/index.html` setzt ihn\ndeshalb an jedem Hero individuell direkt am `<img>` (z. B. `object-position:center\n70%`, Zeile 2640). Ohne diesen Input wäre die Komponente für Porträt-lastige\nMotive unbrauchbar: `object-fit:cover` beschneidet das Bild sobald der\n`max-height`-Cap greift (siehe `.hero-image-media`), und ohne Steuerung landet\nder Ausschnitt zufällig in der Bildmitte. Als Style-Binding am `<img>`, nicht als\nKlasse — es gibt in der CSS-Schicht keine Modifier-Klassen dafür, nur den\nInline-Weg, den das Mockup selbst nutzt.\n\n**Entscheidung 4 — `tabindex`/`id` sind nicht Teil der Komponente.** Das Mockup\nsetzt beides am `<figure>` (`id=\"ep-landing-main\" tabindex=\"-1\"`, z. B. Zeile\n9406) als Sprungziel des Skip-Links. Das Sprungziel ist eine Entscheidung der\nSeite (welches Element „Hauptinhalt“ ist, hängt vom Seitenaufbau ab, nicht vom\nHero selbst), deshalb setzt der Konsument beides am `<cds-hero-image>`-Host.\nDamit dieses Sprungziel eine eigene Box hat, ist der Host per\n`:host{display:block}` selbst ein Block (Muster wie `cds-footer-main`): Als\nunbekanntes Element wäre er `display:inline` mit herausgebrochenem Block-Kind\nund hätte keine brauchbare Box für Fokus und Scroll-Position (ADR-0008, Fall 2).",
589
589
  "docgen": {
590
590
  "$ref": "../services/core/docgen/komponenten-hero-hero-bild.json#/components/komponenten-hero-hero-bild"
591
591
  },
@@ -606,7 +606,7 @@
606
606
  "komponenten-hero-störer": {
607
607
  "id": "komponenten-hero-störer",
608
608
  "name": "StoererSetComponent",
609
- "description": "StoererSet (cds-stoerer-set) — Wrapper um `.stoerer-set` / `.stoerer-list` aus\ncss/components.css (css/components.css:926–974): das Set aus ein bis drei\n`<cds-stoerer>`-Kacheln, das auf der Startseite oben rechts über dem Hero-Bild\nliegt. Der Positionsrahmen `.stoerer-hero` (umschließt Hero-Bild UND Set,\n`container-type:inline-size` für die Container-Query-Schwelle) ist bewusst NICHT\nTeil dieser Komponente, sondern bleibt beim Konsumenten (siehe\n`.scratch/angular-seitenbausteine/issues/03-stoerer.md`): er umschließt Hero UND\nSet gemeinsam, das könnte diese Komponente als internes Detail nicht leisten, ohne\ndas Hero-Bild selbst zu kennen.\n\n**Entscheidung — `<li>` ohne strukturelles Wrapper-Element.** `.stoerer-list` ist\nein `<ul>`; nach HTML-AAM bekommt ein `<li>` die implizite Rolle `listitem` nur als\ndirektes Kind eines `<ul>`/`<ol>`/`<menu>` — Screenreader kündigen die Liste sonst\nnicht mit Anzahl an. Ein einfaches `<ng-content>` hätte `<cds-stoerer>` selbst\nzwischen `<ul>` und `<li>` geschoben (kein direktes Kind mehr), und ein\n`display:contents`-Wrapper verbietet sich als neues CSS (ADR-0001). Gelöst über\ndasselbe Muster, das `cds-area-tabs`/`cds-area-tab` bereits für „projizierte\nKind-Komponente, deren Markup an einer selbst bestimmten Stelle im eigenen Template\nlandet“ etabliert (`area-tabs/area-tabs.component.ts`): `cds-stoerer` rendert nicht\nin sich selbst, sondern in ein `<ng-template>` und liefert es als `TemplateRef`\n(`stoerer.component.ts`). Diese Komponente liest die projizierten `<cds-stoerer>`\nüber `contentChildren()` und setzt ihr Template per `ngTemplateOutlet` direkt in\nein selbst gerendertes `<li>` ein. Im laufenden Storybook geprüft (`Komponenten/Hero/\nStörer` → Story „Interaktiv“, DOM live im Browser per `querySelector` abgefragt,\nzusätzlich in der Play-Funktion der Story gepinnt): `<ul class=\"stoerer-list\">`\nhat ausschließlich `<li>` als direkte Kinder (`ul.stoerer-list > *` → `[\"LI\",\"LI\"]`),\n`<cds-stoerer>` selbst taucht im gerenderten DOM nirgends auf — Angular hängt\nunprojizierten Content ohne passendes `<ng-content>`-Ziel gar nicht erst ein, exakt\nwie bei `<cds-area-tab>` innerhalb von `<cds-area-tabs>`. Ein `items`-Array (die im\nTicket vermerkte Alternative) hätte denselben DOM-Baum ergeben, aber den\nKonsumenten gezwungen, das projizierte Icon jeder Kachel in ein Datenobjekt\numzuziehen; mit dieser Lösung bleibt die deklarative, Content-projizierende API\nerhalten.\n\nVerwendungsguidance dieser Gruppe: siehe Hero-Bild (`komponenten-hero-hero-bild--verwendung`).",
609
+ "description": "StoererSet (cds-stoerer-set) — Wrapper um `.stoerer-set` / `.stoerer-list` aus\ncss/components.css (css/components.css:926–974): das Set aus ein bis drei\n`<cds-stoerer>`-Kacheln, das auf der Startseite oben rechts über dem Hero-Bild\nliegt. Der Positionsrahmen `.stoerer-hero` (umschließt Hero-Bild UND Set,\n`container-type:inline-size` für die Container-Query-Schwelle) ist bewusst NICHT\nTeil dieser Komponente, sondern bleibt beim Konsumenten: er umschließt Hero UND\nSet gemeinsam, das könnte diese Komponente als internes Detail nicht leisten, ohne\ndas Hero-Bild selbst zu kennen.\n\n**Entscheidung — `<li>` ohne strukturelles Wrapper-Element.** `.stoerer-list` ist\nein `<ul>`; nach HTML-AAM bekommt ein `<li>` die implizite Rolle `listitem` nur als\ndirektes Kind eines `<ul>`/`<ol>`/`<menu>` — Screenreader kündigen die Liste sonst\nnicht mit Anzahl an. Ein einfaches `<ng-content>` hätte `<cds-stoerer>` selbst\nzwischen `<ul>` und `<li>` geschoben (kein direktes Kind mehr), und ein\n`display:contents`-Wrapper verbietet sich als neues CSS (ADR-0001). Gelöst über\ndasselbe Muster, das `cds-area-tabs`/`cds-area-tab` bereits für „projizierte\nKind-Komponente, deren Markup an einer selbst bestimmten Stelle im eigenen Template\nlandet“ etabliert (`area-tabs/area-tabs.component.ts`): `cds-stoerer` rendert nicht\nin sich selbst, sondern in ein `<ng-template>` und liefert es als `TemplateRef`\n(`stoerer.component.ts`). Diese Komponente liest die projizierten `<cds-stoerer>`\nüber `contentChildren()` und setzt ihr Template per `ngTemplateOutlet` direkt in\nein selbst gerendertes `<li>` ein. Im laufenden Storybook geprüft (`Komponenten/Hero/\nStörer` → Story „Interaktiv“, DOM live im Browser per `querySelector` abgefragt,\nzusätzlich in der Play-Funktion der Story gepinnt): `<ul class=\"stoerer-list\">`\nhat ausschließlich `<li>` als direkte Kinder (`ul.stoerer-list > *` → `[\"LI\",\"LI\"]`),\n`<cds-stoerer>` selbst taucht im gerenderten DOM nirgends auf — Angular hängt\nunprojizierten Content ohne passendes `<ng-content>`-Ziel gar nicht erst ein, exakt\nwie bei `<cds-area-tab>` innerhalb von `<cds-area-tabs>`. Ein `items`-Array (die\nerwogene Alternative) hätte denselben DOM-Baum ergeben, aber den\nKonsumenten gezwungen, das projizierte Icon jeder Kachel in ein Datenobjekt\numzuziehen; mit dieser Lösung bleibt die deklarative, Content-projizierende API\nerhalten.\n\nVerwendungsguidance dieser Gruppe: siehe Hero-Bild (`komponenten-hero-hero-bild--verwendung`).",
610
610
  "docgen": {
611
611
  "$ref": "../services/core/docgen/komponenten-hero-störer.json#/components/komponenten-hero-störer"
612
612
  },
@@ -703,7 +703,7 @@
703
703
  "seitenmuster-wissensbeitrag-article-header": {
704
704
  "id": "seitenmuster-wissensbeitrag-article-header",
705
705
  "name": "ArticleHeaderComponent",
706
- "description": "ArticleHeader (`cds-article-header`) — Wrapper um `.article-header` aus\ncss/components.css (css/components.css:1486–1490, 1508–1519): der zentrierte Kopf\neines Wissensbeitrags (Breadcrumb, optionale Pill, H1, Lead, Meta-Strip). Elftes\nTicket der Seitenbausteine-Serie. Hauptvorlage ist\n`storybook-angular/src/docs/seitenmuster/wissensbeitrag.mdx`, Abschnitt „Article\nHeader“ — dort steht das für dieses Ticket verbindliche Markup samt Begründung,\nnicht nur das Mockup in `docs/index.html`.\n\n**Breadcrumb sitzt im Header — abweichend von der allgemeinen Navigationsregel,\nbewusst der Ticket-Vorlage folgend.** `docs/index.html:2423` dokumentiert für\nUnterseiten allgemein, wörtlich: „Den Breadcrumb nicht in einen zentrierten\n`.article-header` einbetten, sonst wird er mittig ausgerichtet und bekommt einen\nabweichenden Abstand zur Nav“ (Tabellenzeile „Nicht“ der Breadcrumb-Doku) — und\nausgezählt\nsind es genau die beiden EINZIGEN vollständigen Beispielseiten mit\n`.article-header` (Stellenanzeige `docs/index.html:11035`, Wissensbeitrag · KI\n`docs/index.html:15020`), die dieser Regel folgen: beide setzen den Breadcrumb in\neine EIGENE, linksbündige `.ep-section` VOR dem Header (`docs/index.html:\n11023–11031` bzw. `15010–15015`). Dem stehen 6 ISOLIERTE Doku-Demos gegenüber, die\nden Breadcrumb INNERHALB von `.article-header` zeigen: 5 kleine Referenz-Boxen in\nder Doku-Sektion selbst (`docs/index.html:7850`, `8304`, `8331`, `8361`, `8392` —\nohne Topnav/Hero/Footer, reine Bauteil-Illustrationen) plus das Code-Beispiel in\n`wissensbeitrag.mdx`. Das Ticket benennt genau diese Doku (nicht die\nBeispielseiten) als Hauptvorlage. Diese Komponente folgt der Ticket-Vorgabe\n(Breadcrumb als Input, im Header) und dokumentiert den Widerspruch als eigenen\nBefund\n(`.scratch/angular-seitenbausteine/issues/20-breadcrumb-in-oder-vor-article-header.md`).\nKonsumenten, die der allgemeinen Regel folgen wollen (wie die beiden echten\nBeispielseiten), lassen `breadcrumb` leer und setzen die Leiste selbst davor\n(siehe Story „Ohne Breadcrumb“).\n\n**Letzter Breadcrumb-Eintrag immer ohne Link, mit `aria-current=\"page\"`** — so vom\nTicket als Akzeptanzkriterium gefordert. Die beiden Wissensbeitrag-Inline-Beispiele\n(`docs/index.html:7850–7856`, `wissensbeitrag.mdx`) zeigen dafür nur 2 Einträge,\nBEIDE als `<a>`, ohne `aria-current` — eine zur Kürze vereinfachte Doku-Skizze,\nkein vollständiges Barrierefreiheits-Beispiel. Das volle, korrekte Muster (Trenner\ndekorativ, letzter Eintrag `<span aria-current=\"page\">` ohne Link) steht an\nanderer Stelle exakt so vor (`docs/index.html:2406–2412` allgemeine\nBreadcrumb-Doku, `docs/index.html:11023–11030` Stellenanzeige) und ist zusätzlich\neine explizite Akzeptanzbedingung des Tickets — die Komponente folgt deshalb\ndiesem Muster für den letzten Array-Eintrag, unabhängig von einem dort eventuell\ngesetzten `href`.\n\n**Keine bereichsgefärbte Breadcrumb-Verlinkung (`.t-{area}`).** Die\nInline-Beispiele färben ihren zweiten (dort letzten) Eintrag mit `.t-ki`/`.t-es`/\n`.t-wo`; das Ticket sieht dafür aber kein API-Feld vor (`{label, href?}`, kein\n`area` pro Eintrag), und eine Zuordnung „Bereichsfarbe auf den vorletzten\nEintrag“ wäre geraten, nicht aus der Doku ableitbar — zumal der gefärbte Eintrag\ndort ein Link ist, kein `aria-current`-Element, also nicht deckungsgleich mit dem\noben umgesetzten Muster. Bleibt bewusst außen vor.\n\n**Pill über `cds-pill`, kein direkt komponiertes `<span class=\"pill\">`.** Geprüft\nper `npx storybook tools docs show --id komponenten-chips-badges-pills-pill`:\n`PillComponent`s Host ist `<cds-pill>`, `.pill` sitzt eine Ebene darunter auf\neinem inneren `<span>` (anders als bei `FeaturedCardComponent`, wo genau das wegen\n`.card-featured-body>.pill`-Kindselektoren nicht ginge). `.article-header`/`.pill`\nhaben keinen solchen Kindselektor (css/components.css:1486–1507 geprüft) — die\nzusätzliche Ebene ist hier folgenlos, deshalb bleibt `cds-pill` als echte\nKomponente eingesetzt, keine Klassen-Duplikation nötig.\n\n**Pille rendert nur, wenn `area` UND `pill` gesetzt sind — kein erfundener\nDefault.** `PillComponent.area` ist als `CdsArea` mit einem eigenen Default\n(`'ki'`) typisiert; diese Komponente führt `area` dagegen als `CdsArea | undefined`\n(keine Konfigurations-Vorgabe laut Ticket-API). Ein erster Entwurf reichte\n`area()` per `as CdsArea`-Cast durch, damit ein `undefined`-Binding\n`cds-pill`s eigenen Default überschreiben kann — eine Typ-Lüge, um einen Fall\nabzudecken, der beim Nachzählen gar nicht vorkommt: **alle 9** `.pill`-Vorkommen\nim Article-Header-Kontext (`docs/index.html:7856, 7912–7914, 8310, 8337, 8367,\n8398, 15021`) tragen ein `data-area`, keines ohne Bereich. Die Pille rendert\ndeshalb nur innerhalb von `@if (area(); as pillArea)` (narrowt `CdsArea |\nundefined` sauber auf `CdsArea`, ohne Cast) — ist `pill()` gesetzt, `area()`\naber nicht, bleibt die Pille schlicht weg, statt eine geratene Bereichsfarbe zu\nzeigen. Gepinnt in der Story „Pille ohne Bereich“.\n\n**`pillAriaLabel`, wie bei `FeaturedCardComponent`.** `PillComponent`s eigener\nDefault („Bereich `<label>`“) passt nur, wenn die Pille einen Bereichsnamen trägt\n(Stellenanzeige, `docs/index.html:11036`: „Bereich Effektive Software“). Im\nWissensbeitrag trägt die Pille dagegen die Lesezeit (`wissensbeitrag.mdx`,\nAbschnitt „Pill · Lesezeit“: `aria-label=\"Lesezeit 8 Minuten\"`) — ein Text, den\ndie Komponente nicht zuverlässig aus einem freien `pill`-String wie\n„8 min Lesezeit“ herleiten kann (keine Zahl zu parsen, keine Garantie, dass der\nText überhaupt eine Lesezeit ist). `pillAriaLabel` bleibt deshalb optional, ohne\neigenen Default: ungesetzt geht `undefined` direkt an `cds-pill`s `ariaLabel`\ndurch, das dann auf seinen eigenen Default („Bereich `<pill>`“) zurückfällt —\nkeine Logik-Duplikation nötig, weil hier die echte `PillComponent` verwendet\nwird, keine Klassen-Nachbildung.\n\n**`date`/`dateLabel` getrennt, ANDERS als `cds-stoerer`.** `cds-stoerer`\nformatiert ein reines ISO-Datum selbst nach `de-DE`\n(`stoerer.component.ts:formattedDate`, inkl. manuellem Aufbau der\n`Date`-Komponenten gegen den UTC-Mitternacht-Fallstrick von `new Date(iso)`).\nDiese Komponente tut das NICHT: `date` liefert nur den maschinenlesbaren\n`datetime`-Wert, `dateLabel` die sichtbare Schreibweise — vom Ticket ausdrücklich\nals zwei getrennte Inputs vorgegeben. Begründung der Abweichung: Erstens deckt\nsich das mit jeder realen Instanz (6 von 6 `.article-meta-date`-Vorkommen tragen\neinen redaktionell gesetzten Text wie „13. Mai 2026“, keinen von\n`Intl.DateTimeFormat` erzeugten) — anders als der Störer, dessen Kacheln NUR ein\nISO-Datum ohne separaten Text führen. Zweitens umgeht die Trennung den\nZeitzonen-Fallstrick vollständig, statt ihn zu beheben: ohne eigene Formatierung\nkonstruiert diese Komponente nie ein `Date`-Objekt aus `date()`, die\nUTC-Mitternacht-Falle kann hier gar nicht erst auftreten. Ist `dateLabel` leer,\nzeigt die Komponente `date()` unverändert als sichtbaren Text (bewusst als\nISO-Form, nicht geraten formatiert) — in der Story „Interaktiv“ als Warnfall extra\nbenannt, damit niemand das versehentlich auf eine Produktionsseite bringt.\n\n**Meta-Strip nur, wenn es etwas zu zeigen gibt.** `authorName`/`date` sind laut\nTicket-API Beiwerk (Default `''`); ausgezählt haben alle 6 realen\n`.article-meta`-Vorkommen sowohl Autor als auch Datum, eine Stellenanzeige\n(`docs/index.html:11035`) nutzt `.article-header` dagegen KOMPLETT OHNE\n`.article-meta` (dort ein `<dl>` aus Job-Fakten statt Autor/Datum) — dieser\nabweichenden Verwendung folgt diese Komponente nicht (sie hat in der Ticket-API\nkeinen Platz), sie belegt aber, dass ein leerer Meta-Strip ein realer Fall ist.\n`.article-meta` (und darin `.article-meta-author`/`.article-meta-sep`) rendern\ndeshalb nur, wenn `authorName()` bzw. `date()` tatsächlich etwas liefern; der\nTrenner nur, wenn beide Seiten vorhanden sind.\n\n**Avatar projiziert über `[cdsAvatar]`, PLUS `cds-avatar-stack`.** Die\nTicket-Skizze nennt nur `<ng-content select=\"[cdsAvatar]\">`; ergänzt um den\nTag-Selektor `cds-avatar-stack`, weil `AvatarStackComponent` (siehe dort) ein\nElement-Selektor ist, kein `[cdsAvatar]`-Attribut trägt und sonst nicht in den\nMeta-Strip projiziert werden könnte — nötig für die vom Ticket geforderte Story\n„Mehrere Autor:innen“.\n\n**Host als Block.** `.article-header` sitzt am Host und setzt nur\n`max-width:880px;margin:0 auto`. An einem unbekannten, also `display:inline`\ngerenderten Element greift beides nicht, die Block-Kinder liefen über die\n880 px hinaus. `:host{display:block}` wie bei `cds-hero-image` und\n`cds-footer-main`; Story „Breite begrenzt“ pinnt das.",
706
+ "description": "ArticleHeader (`cds-article-header`) — Wrapper um `.article-header` aus\ncss/components.css (css/components.css:1486–1490, 1508–1519): der zentrierte Kopf\neines Wissensbeitrags (Breadcrumb, optionale Pill, H1, Lead, Meta-Strip).\nHauptvorlage ist\n`storybook-angular/src/docs/seitenmuster/wissensbeitrag.mdx`, Abschnitt „Article\nHeader“ — dort steht das verbindliche Markup samt Begründung,\nnicht nur das Mockup in `docs/index.html`.\n\n**Breadcrumb sitzt im Header — abweichend von der allgemeinen Navigationsregel,\nbewusst der Hauptvorlage folgend.** `docs/index.html:2423` dokumentiert für\nUnterseiten allgemein, wörtlich: „Den Breadcrumb nicht in einen zentrierten\n`.article-header` einbetten, sonst wird er mittig ausgerichtet und bekommt einen\nabweichenden Abstand zur Nav“ (Tabellenzeile „Nicht“ der Breadcrumb-Doku) — und\nausgezählt\nsind es genau die beiden EINZIGEN vollständigen Beispielseiten mit\n`.article-header` (Stellenanzeige `docs/index.html:11035`, Wissensbeitrag · KI\n`docs/index.html:15020`), die dieser Regel folgen: beide setzen den Breadcrumb in\neine EIGENE, linksbündige `.ep-section` VOR dem Header (`docs/index.html:\n11023–11031` bzw. `15010–15015`). Dem stehen 6 ISOLIERTE Doku-Demos gegenüber, die\nden Breadcrumb INNERHALB von `.article-header` zeigen: 5 kleine Referenz-Boxen in\nder Doku-Sektion selbst (`docs/index.html:7850`, `8304`, `8331`, `8361`, `8392` —\nohne Topnav/Hero/Footer, reine Bauteil-Illustrationen) plus das Code-Beispiel in\n`wissensbeitrag.mdx`. Diese Komponente folgt bewusst dieser Doku, nicht den\nBeispielseiten (Breadcrumb als Input, im Header), und dokumentiert den\nWiderspruch als eigenen Befund.\nKonsumenten, die der allgemeinen Regel folgen wollen (wie die beiden echten\nBeispielseiten), lassen `breadcrumb` leer und setzen die Leiste selbst davor\n(siehe Story „Ohne Breadcrumb“).\n\n**Letzter Breadcrumb-Eintrag immer ohne Link, mit `aria-current=\"page\"`.** Die\nbeiden Wissensbeitrag-Inline-Beispiele\n(`docs/index.html:7850–7856`, `wissensbeitrag.mdx`) zeigen dafür nur 2 Einträge,\nBEIDE als `<a>`, ohne `aria-current` — eine zur Kürze vereinfachte Doku-Skizze,\nkein vollständiges Barrierefreiheits-Beispiel. Das volle, korrekte Muster (Trenner\ndekorativ, letzter Eintrag `<span aria-current=\"page\">` ohne Link) steht an\nanderer Stelle exakt so vor (`docs/index.html:2406–2412` allgemeine\nBreadcrumb-Doku, `docs/index.html:11023–11030` Stellenanzeige) — die Komponente\nfolgt deshalb diesem Muster für den letzten Array-Eintrag, unabhängig von einem\ndort eventuell gesetzten `href`.\n\n**Keine bereichsgefärbte Breadcrumb-Verlinkung (`.t-{area}`).** Die\nInline-Beispiele färben ihren zweiten (dort letzten) Eintrag mit `.t-ki`/`.t-es`/\n`.t-wo`; die API sieht dafür aber kein Feld vor (`{label, href?}`, kein\n`area` pro Eintrag), und eine Zuordnung „Bereichsfarbe auf den vorletzten\nEintrag“ wäre geraten, nicht aus der Doku ableitbar — zumal der gefärbte Eintrag\ndort ein Link ist, kein `aria-current`-Element, also nicht deckungsgleich mit dem\noben umgesetzten Muster. Bleibt bewusst außen vor.\n\n**Pill über `cds-pill`, kein direkt komponiertes `<span class=\"pill\">`.** Geprüft\nper `npx storybook tools docs show --id komponenten-chips-badges-pills-pill`:\n`PillComponent`s Host ist `<cds-pill>`, `.pill` sitzt eine Ebene darunter auf\neinem inneren `<span>` (anders als bei `FeaturedCardComponent`, wo genau das wegen\n`.card-featured-body>.pill`-Kindselektoren nicht ginge). `.article-header`/`.pill`\nhaben keinen solchen Kindselektor (css/components.css:1486–1507 geprüft) — die\nzusätzliche Ebene ist hier folgenlos, deshalb bleibt `cds-pill` als echte\nKomponente eingesetzt, keine Klassen-Duplikation nötig.\n\n**Pille rendert nur, wenn `area` UND `pill` gesetzt sind — kein erfundener\nDefault.** `PillComponent.area` ist als `CdsArea` mit einem eigenen Default\n(`'ki'`) typisiert; diese Komponente führt `area` dagegen als `CdsArea | undefined`\n(kein eigener Default vorgegeben). Ein erster Entwurf reichte\n`area()` per `as CdsArea`-Cast durch, damit ein `undefined`-Binding\n`cds-pill`s eigenen Default überschreiben kann — eine Typ-Lüge, um einen Fall\nabzudecken, der beim Nachzählen gar nicht vorkommt: **alle 9** `.pill`-Vorkommen\nim Article-Header-Kontext (`docs/index.html:7856, 7912–7914, 8310, 8337, 8367,\n8398, 15021`) tragen ein `data-area`, keines ohne Bereich. Die Pille rendert\ndeshalb nur innerhalb von `@if (area(); as pillArea)` (narrowt `CdsArea |\nundefined` sauber auf `CdsArea`, ohne Cast) — ist `pill()` gesetzt, `area()`\naber nicht, bleibt die Pille schlicht weg, statt eine geratene Bereichsfarbe zu\nzeigen. Gepinnt in der Story „Pille ohne Bereich“.\n\n**`pillAriaLabel`, wie bei `FeaturedCardComponent`.** `PillComponent`s eigener\nDefault („Bereich `<label>`“) passt nur, wenn die Pille einen Bereichsnamen trägt\n(Stellenanzeige, `docs/index.html:11036`: „Bereich Effektive Software“). Im\nWissensbeitrag trägt die Pille dagegen die Lesezeit (`wissensbeitrag.mdx`,\nAbschnitt „Pill · Lesezeit“: `aria-label=\"Lesezeit 8 Minuten\"`) — ein Text, den\ndie Komponente nicht zuverlässig aus einem freien `pill`-String wie\n„8 min Lesezeit“ herleiten kann (keine Zahl zu parsen, keine Garantie, dass der\nText überhaupt eine Lesezeit ist). `pillAriaLabel` bleibt deshalb optional, ohne\neigenen Default: ungesetzt geht `undefined` direkt an `cds-pill`s `ariaLabel`\ndurch, das dann auf seinen eigenen Default („Bereich `<pill>`“) zurückfällt —\nkeine Logik-Duplikation nötig, weil hier die echte `PillComponent` verwendet\nwird, keine Klassen-Nachbildung.\n\n**`date`/`dateLabel` getrennt, ANDERS als `cds-stoerer`.** `cds-stoerer`\nformatiert ein reines ISO-Datum selbst nach `de-DE`\n(`stoerer.component.ts:formattedDate`, inkl. manuellem Aufbau der\n`Date`-Komponenten gegen den UTC-Mitternacht-Fallstrick von `new Date(iso)`).\nDiese Komponente tut das NICHT: `date` liefert nur den maschinenlesbaren\n`datetime`-Wert, `dateLabel` die sichtbare Schreibweise — bewusst als zwei\ngetrennte Inputs. Begründung der Abweichung: Erstens deckt\nsich das mit jeder realen Instanz (6 von 6 `.article-meta-date`-Vorkommen tragen\neinen redaktionell gesetzten Text wie „13. Mai 2026“, keinen von\n`Intl.DateTimeFormat` erzeugten) — anders als der Störer, dessen Kacheln NUR ein\nISO-Datum ohne separaten Text führen. Zweitens umgeht die Trennung den\nZeitzonen-Fallstrick vollständig, statt ihn zu beheben: ohne eigene Formatierung\nkonstruiert diese Komponente nie ein `Date`-Objekt aus `date()`, die\nUTC-Mitternacht-Falle kann hier gar nicht erst auftreten. Ist `dateLabel` leer,\nzeigt die Komponente `date()` unverändert als sichtbaren Text (bewusst als\nISO-Form, nicht geraten formatiert) — in der Story „Interaktiv“ als Warnfall extra\nbenannt, damit niemand das versehentlich auf eine Produktionsseite bringt.\n\n**Meta-Strip nur, wenn es etwas zu zeigen gibt.** `authorName`/`date` sind bewusst\nBeiwerk (Default `''`); ausgezählt haben alle 6 realen\n`.article-meta`-Vorkommen sowohl Autor als auch Datum, eine Stellenanzeige\n(`docs/index.html:11035`) nutzt `.article-header` dagegen KOMPLETT OHNE\n`.article-meta` (dort ein `<dl>` aus Job-Fakten statt Autor/Datum) — dieser\nabweichenden Verwendung folgt diese Komponente nicht (dafür ist in der API kein\nFeld vorgesehen), sie belegt aber, dass ein leerer Meta-Strip ein realer Fall ist.\n`.article-meta` (und darin `.article-meta-author`/`.article-meta-sep`) rendern\ndeshalb nur, wenn `authorName()` bzw. `date()` tatsächlich etwas liefern; der\nTrenner nur, wenn beide Seiten vorhanden sind.\n\n**Avatar projiziert über `[cdsAvatar]`, PLUS `cds-avatar-stack`.** Der\nursprüngliche Entwurf nennt nur `<ng-content select=\"[cdsAvatar]\">`; ergänzt um den\nTag-Selektor `cds-avatar-stack`, weil `AvatarStackComponent` (siehe dort) ein\nElement-Selektor ist, kein `[cdsAvatar]`-Attribut trägt und sonst nicht in den\nMeta-Strip projiziert werden könnte — nötig für die Story\n„Mehrere Autor:innen“.\n\n**Host als Block.** `.article-header` sitzt am Host und setzt nur\n`max-width:880px;margin:0 auto`. An einem unbekannten, also `display:inline`\ngerenderten Element greift beides nicht, die Block-Kinder liefen über die\n880 px hinaus. `:host{display:block}` wie bei `cds-hero-image` und\n`cds-footer-main`; Story „Breite begrenzt“ pinnt das.",
707
707
  "docgen": {
708
708
  "$ref": "../services/core/docgen/seitenmuster-wissensbeitrag-article-header.json#/components/seitenmuster-wissensbeitrag-article-header"
709
709
  },
@@ -714,7 +714,7 @@
714
714
  "seitenmuster-wissensbeitrag-avatar": {
715
715
  "id": "seitenmuster-wissensbeitrag-avatar",
716
716
  "name": "AvatarComponent",
717
- "description": "Avatar (`div[cdsAvatar]`) — Wrapper um `.article-avatar` aus css/components.css\n(css/components.css:1520–1536): der Initialen-/Foto-Kreis im Meta-Strip eines\nWissensbeitrags (`.article-meta-author`), in der Author-Card (`-lg`) und im\nTeam-Tile-Grid (`-xl`). Elftes Ticket der Seitenbausteine-Serie.\n\n**Attributselektor (ADR-0008), gemessen an der Geschwister-Kette.** Ausgezählt in\n`docs/index.html`: 8 der 11 Instanzen der Grundgröße sitzen als direkte, adjazente\nGeschwister in einer von 3 `.article-avatar-stack`-Kacheln (2 Avatare in Zeilen\n8343–8344, je 3 in Zeilen 8373–8375 und 8404–8406) — genau dort greift\n`.article-avatar-stack .article-avatar + .article-avatar`\n(css/components.css:1540–1541), eine Geschwister-Kette (Kriterium `spec.md` 2b).\nEin Element-Selektor `cds-avatar`, der `.article-avatar` auf einem INNEREN Element\nrendert (wie `cds-facts` es mit `<dl>` tut, weil `<dl>` ein Custom Element nicht\nannehmen kann), würde diese Kette brechen: die beiden `.article-avatar`-Knoten\nlägen dann je eine Ebene UNTER ihrem eigenen `<cds-avatar>`-Host und wären keine\nGeschwister mehr — `+` verlangt denselben Elternknoten, egal wie dicht die Hosts\nselbst nebeneinanderstehen. Eine zweite, ebenfalls tragfähige Variante\n(Element-Selektor MIT `host: { class: 'article-avatar' }`, ohne innere\nWrapper-Ebene) hätte dieselbe Geschwister-Kette ebenso erfüllt — verworfen zugunsten\ndes Attributselektors nur wegen des zweiten, unabhängigen Grundes unten, nicht weil\nsie die Kette nicht gelöst hätte.\n\n**Gemessen, nicht nur hergeleitet.** Story „Avatar-Stapel“ (Play-Funktion) rendert\ndrei `div[cdsAvatar]` in einem `cds-avatar-stack` und misst\n`getBoundingClientRect()` der drei `.article-avatar`-Knoten: bei 40 px Kachelbreite\nund `margin-left:-12px` (css/components.css:1541) überlappen der zweite und dritte\nKreis den jeweils vorherigen um 12 px (Differenz der `left`-Koordinaten = 28 px,\nnicht 40 px) — plus Screenshot-Baseline (`visual-snapshots/…avatar-stapel.png`) als\nvisuelle Bestätigung, nicht nur die Zahl.\n\n**Zweiter, unabhängiger Grund: Wiederverwendung als Projektions-Selektor.**\n`cds-article-header` projiziert den Avatar über\n`<ng-content select=\"[cdsAvatar]\">` (siehe dessen Klassendoku) — dasselbe Attribut,\ndas hier den Host selektiert, dient dem Elternteil zugleich als\nContent-Projection-Selektor. Ein Element-Selektor bräuchte dafür eine zusätzliche,\neigens erfundene Marker-Klasse; der Attributselektor liefert die Zielscheibe für\nbeide Zwecke aus einer Hand, ohne dass der Konsument zwei Attribute schreiben muss.\n\n**Initialen werden aus `name` abgeleitet, kein eigener Input** (Ticket-Vorgabe):\nerstes Zeichen des ersten und des letzten durch Whitespace getrennten Worts, groß\n— ein einzelnes Wort liefert dessen erste zwei Zeichen. Zwei Inputs für dieselbe\nInformation (Name UND Initialen) könnten auseinanderlaufen, sobald nur eines\ngepflegt wird.\n\n**`aria-hidden=\"true\"` fest verdrahtet, kein Input.** Ausgezählt: von 53\n`.article-avatar`-Instanzen (11 Grundgröße + 17 `-lg` + 25 `-xl`) tragen 45 das\nAttribut direkt am Element; die restlichen 8 sind genau die Grundgrößen-Avatare\nINNERHALB von `.article-avatar-stack`, wo bereits der Stack-Container selbst\n`aria-hidden=\"true\"` trägt (siehe `AvatarStackComponent`) — dort wäre ein zweites\n`aria-hidden` am Kind redundant, aber unschädlich: verschachteltes `aria-hidden`\nändert nichts am Ergebnis, der Teilbaum ist so oder so für Assistenztechnologie\nunsichtbar. Der Avatar trägt es deshalb immer: 100 % Deckung mit dem Mockup im\nAlleinstand, harmlose Redundanz im Stapel.",
717
+ "description": "Avatar (`div[cdsAvatar]`) — Wrapper um `.article-avatar` aus css/components.css\n(css/components.css:1520–1536): der Initialen-/Foto-Kreis im Meta-Strip eines\nWissensbeitrags (`.article-meta-author`), in der Author-Card (`-lg`) und im\nTeam-Tile-Grid (`-xl`).\n\n**Attributselektor (ADR-0008), gemessen an der Geschwister-Kette.** Ausgezählt in\n`docs/index.html`: 8 der 11 Instanzen der Grundgröße sitzen als direkte, adjazente\nGeschwister in einer von 3 `.article-avatar-stack`-Kacheln (2 Avatare in Zeilen\n8343–8344, je 3 in Zeilen 8373–8375 und 8404–8406) — genau dort greift\n`.article-avatar-stack .article-avatar + .article-avatar`\n(css/components.css:1540–1541), eine Geschwister-Kette.\nEin Element-Selektor `cds-avatar`, der `.article-avatar` auf einem INNEREN Element\nrendert (wie `cds-facts` es mit `<dl>` tut, weil `<dl>` ein Custom Element nicht\nannehmen kann), würde diese Kette brechen: die beiden `.article-avatar`-Knoten\nlägen dann je eine Ebene UNTER ihrem eigenen `<cds-avatar>`-Host und wären keine\nGeschwister mehr — `+` verlangt denselben Elternknoten, egal wie dicht die Hosts\nselbst nebeneinanderstehen. Eine zweite, ebenfalls tragfähige Variante\n(Element-Selektor MIT `host: { class: 'article-avatar' }`, ohne innere\nWrapper-Ebene) hätte dieselbe Geschwister-Kette ebenso erfüllt — verworfen zugunsten\ndes Attributselektors nur wegen des zweiten, unabhängigen Grundes unten, nicht weil\nsie die Kette nicht gelöst hätte.\n\n**Gemessen, nicht nur hergeleitet.** Story „Avatar-Stapel“ (Play-Funktion) rendert\ndrei `div[cdsAvatar]` in einem `cds-avatar-stack` und misst\n`getBoundingClientRect()` der drei `.article-avatar`-Knoten: bei 40 px Kachelbreite\nund `margin-left:-12px` (css/components.css:1541) überlappen der zweite und dritte\nKreis den jeweils vorherigen um 12 px (Differenz der `left`-Koordinaten = 28 px,\nnicht 40 px) — plus Screenshot-Baseline (`visual-snapshots/…avatar-stapel.png`) als\nvisuelle Bestätigung, nicht nur die Zahl.\n\n**Zweiter, unabhängiger Grund: Wiederverwendung als Projektions-Selektor.**\n`cds-article-header` projiziert den Avatar über\n`<ng-content select=\"[cdsAvatar]\">` (siehe dessen Klassendoku) — dasselbe Attribut,\ndas hier den Host selektiert, dient dem Elternteil zugleich als\nContent-Projection-Selektor. Ein Element-Selektor bräuchte dafür eine zusätzliche,\neigens erfundene Marker-Klasse; der Attributselektor liefert die Zielscheibe für\nbeide Zwecke aus einer Hand, ohne dass der Konsument zwei Attribute schreiben muss.\n\n**Initialen werden aus `name` abgeleitet, kein eigener Input:**\nerstes Zeichen des ersten und des letzten durch Whitespace getrennten Worts, groß\n— ein einzelnes Wort liefert dessen erste zwei Zeichen. Zwei Inputs für dieselbe\nInformation (Name UND Initialen) könnten auseinanderlaufen, sobald nur eines\ngepflegt wird.\n\n**`aria-hidden=\"true\"` fest verdrahtet, kein Input.** Ausgezählt: von 53\n`.article-avatar`-Instanzen (11 Grundgröße + 17 `-lg` + 25 `-xl`) tragen 45 das\nAttribut direkt am Element; die restlichen 8 sind genau die Grundgrößen-Avatare\nINNERHALB von `.article-avatar-stack`, wo bereits der Stack-Container selbst\n`aria-hidden=\"true\"` trägt (siehe `AvatarStackComponent`) — dort wäre ein zweites\n`aria-hidden` am Kind redundant, aber unschädlich: verschachteltes `aria-hidden`\nändert nichts am Ergebnis, der Teilbaum ist so oder so für Assistenztechnologie\nunsichtbar. Der Avatar trägt es deshalb immer: 100 % Deckung mit dem Mockup im\nAlleinstand, harmlose Redundanz im Stapel.",
718
718
  "docgen": {
719
719
  "$ref": "../services/core/docgen/seitenmuster-wissensbeitrag-avatar.json#/components/seitenmuster-wissensbeitrag-avatar"
720
720
  },
@@ -725,7 +725,7 @@
725
725
  "seitenmuster-wissensbeitrag-inhaltsverzeichnis": {
726
726
  "id": "seitenmuster-wissensbeitrag-inhaltsverzeichnis",
727
727
  "name": "ArticleTocComponent",
728
- "description": "ArticleToc (`cds-article-toc`) — Wrapper um `.article-toc*` aus css/components.css\n(css/components.css:1577–1589): das aufklappbare Inhaltsverzeichnis am Anfang eines\nWissensbeitrags. Zwölftes Ticket der Seitenbausteine-Serie („Artikel-Körper“).\nAusgezählt: genau 2 reale Vorkommen, beide mit 5 Einträgen und identischem\nsichtbarem Text „Inhalt“ plus identischem `aria-label`\n(`docs/index.html:7948–7960`, Doku-Sektion mit Platzhalter-Hrefs `#gt-article-toc`;\n`docs/index.html:15049–15061`, reale Beispielseite Wissensbeitrag · KI mit echten\nAbschnittsankern wie `#wb-ki-demo`).\n\n**Element-Selektor, ADR-0008-Standardfall.** `.article-toc` sitzt in beiden\nVorkommen als gewöhnlicher Block-Nachfahre direkt in `.article-body`\n(`docs/index.html:7950`, `15051` je eine Ebene über dem `<details>`) — kein\nGrid-/Flex-Kind, keine Layout-Klasse (`col-*`) vom Konsumenten, kein\nGeschwister-Kombinator (`.article-toc` kommt in keiner CSS-Regel mit `+`/`~`\nvor), kein Tag-Wechsel (immer `<details>`). Keines der drei ADR-0008-Kriterien\ngreift; ein Attributselektor brächte hier nichts. Die einzige direkte\nKind-Regel von `.article-body` (`.article-body > p`, css/components.css:1566)\nzielt auf `p`, nicht auf `details` — ein `<cds-article-toc>`-Host zwischen\n`.article-body` und dem `<details>` bricht deshalb keine Selektorkette.\n\n**Natives `<details>`/`<summary>`, kein nachgebautes Disclosure** (Ticket-Vorgabe,\nAkzeptanzkriterium): Tastaturbedienung (Enter/Space auf `<summary>`) und\nToggle-Zustand kommen vollständig vom Browser, das CSS hängt direkt an `[open]`\n(`.article-toc[open] .article-toc-caret`, css/components.css:1583). `<details\nclass=\"article-toc\">` sitzt deshalb im TEMPLATE, nicht am Host — derselbe Grund\nwie bei `cds-compare`s `<details class=\"ep-compare\">` (`compare.component.ts`):\nein `<cds-article-toc>`-Host ist ein unbekanntes Custom Element und kann native\nDisclosure-Semantik nicht annehmen.\n\n**Zweites Vorkommen des `<details>`-mit-drehendem-Caret-Musters, nicht\nzusammengezogen mit `cds-compare` (ADR-0007 §5).** Beide Bauteile teilen die\nForm (natives `<details>`, SVG-Caret, `[open]`-Rotation über\n`transition:transform`), aber nicht die Aufgabe: `cds-compare` trägt eine\nVergleichstabelle mit `columns`/`rows`/`toggled`-Output, dieses Bauteil trägt\neine reine Link-Liste ohne Output (siehe API-Vorgabe des Tickets, die für dieses\nBauteil bewusst keinen `toggled` vorsieht). Zwei gleiche Stellen sind ein\nZufall, kein Muster — eine gemeinsame Basisklasse für zwei Vorkommen hätte nur\ndas `<details>`+Caret-Skelett gebündelt und dabei entweder Verhalten\nvereinheitlicht (ein erzwungener `toggled`-Output ohne Zweck hier) oder so viel\nparametrisiert, dass nichts gewonnen wäre.\n\n**`aria-label` am `<summary>` ist fest verdrahtet, kein Input.** Ausgezählt:\nbeide realen Vorkommen tragen wortgleich\n`aria-label=\"Inhaltsverzeichnis ein- und ausklappen\"` — der sichtbare Text\n„Inhalt“ (`summary`-Input) allein wäre außerhalb des Beitragskontexts nicht\neindeutig, das `aria-label` schreibt deshalb aus, was das Element tut. Genau\ndieser Wortlaut aus dem Mockup übernommen, kein erfundener zugänglicher Name.\n\n**Keine Nummerierung im Markup.** Die sichtbaren Ziffern vor jedem Eintrag\nkommen aus `counter(toc)` auf `.article-toc-list li::before`\n(css/components.css:1586), nicht aus dem `<ol>`-Default (`list-style:none` setzt\nihn zurück) — die Komponente muss dafür nichts weiter tun als `<ol\nclass=\"article-toc-list\">` zu rendern.",
728
+ "description": "ArticleToc (`cds-article-toc`) — Wrapper um `.article-toc*` aus css/components.css\n(css/components.css:1577–1589): das aufklappbare Inhaltsverzeichnis am Anfang eines\nWissensbeitrags. Ausgezählt: genau 2 reale Vorkommen, beide mit 5 Einträgen und identischem\nsichtbarem Text „Inhalt“ plus identischem `aria-label`\n(`docs/index.html:7948–7960`, Doku-Sektion mit Platzhalter-Hrefs `#gt-article-toc`;\n`docs/index.html:15049–15061`, reale Beispielseite Wissensbeitrag · KI mit echten\nAbschnittsankern wie `#wb-ki-demo`).\n\n**Element-Selektor, ADR-0008-Standardfall.** `.article-toc` sitzt in beiden\nVorkommen als gewöhnlicher Block-Nachfahre direkt in `.article-body`\n(`docs/index.html:7950`, `15051` je eine Ebene über dem `<details>`) — kein\nGrid-/Flex-Kind, keine Layout-Klasse (`col-*`) vom Konsumenten, kein\nGeschwister-Kombinator (`.article-toc` kommt in keiner CSS-Regel mit `+`/`~`\nvor), kein Tag-Wechsel (immer `<details>`). Keines der drei ADR-0008-Kriterien\ngreift; ein Attributselektor brächte hier nichts. Die einzige direkte\nKind-Regel von `.article-body` (`.article-body > p`, css/components.css:1566)\nzielt auf `p`, nicht auf `details` — ein `<cds-article-toc>`-Host zwischen\n`.article-body` und dem `<details>` bricht deshalb keine Selektorkette.\n\n**Natives `<details>`/`<summary>`, kein nachgebautes Disclosure:** Tastaturbedienung\n(Enter/Space auf `<summary>`) und\nToggle-Zustand kommen vollständig vom Browser, das CSS hängt direkt an `[open]`\n(`.article-toc[open] .article-toc-caret`, css/components.css:1583). `<details\nclass=\"article-toc\">` sitzt deshalb im TEMPLATE, nicht am Host — derselbe Grund\nwie bei `cds-compare`s `<details class=\"ep-compare\">` (`compare.component.ts`):\nein `<cds-article-toc>`-Host ist ein unbekanntes Custom Element und kann native\nDisclosure-Semantik nicht annehmen.\n\n**Zweites Vorkommen des `<details>`-mit-drehendem-Caret-Musters, nicht\nzusammengezogen mit `cds-compare` (ADR-0007 §5).** Beide Bauteile teilen die\nForm (natives `<details>`, SVG-Caret, `[open]`-Rotation über\n`transition:transform`), aber nicht die Aufgabe: `cds-compare` trägt eine\nVergleichstabelle mit `columns`/`rows`/`toggled`-Output, dieses Bauteil trägt\neine reine Link-Liste ohne Output (die API sieht für dieses\nBauteil bewusst keinen `toggled` vor). Zwei gleiche Stellen sind ein\nZufall, kein Muster — eine gemeinsame Basisklasse für zwei Vorkommen hätte nur\ndas `<details>`+Caret-Skelett gebündelt und dabei entweder Verhalten\nvereinheitlicht (ein erzwungener `toggled`-Output ohne Zweck hier) oder so viel\nparametrisiert, dass nichts gewonnen wäre.\n\n**`aria-label` am `<summary>` ist fest verdrahtet, kein Input.** Ausgezählt:\nbeide realen Vorkommen tragen wortgleich\n`aria-label=\"Inhaltsverzeichnis ein- und ausklappen\"` — der sichtbare Text\n„Inhalt“ (`summary`-Input) allein wäre außerhalb des Beitragskontexts nicht\neindeutig, das `aria-label` schreibt deshalb aus, was das Element tut. Genau\ndieser Wortlaut aus dem Mockup übernommen, kein erfundener zugänglicher Name.\n\n**Keine Nummerierung im Markup.** Die sichtbaren Ziffern vor jedem Eintrag\nkommen aus `counter(toc)` auf `.article-toc-list li::before`\n(css/components.css:1586), nicht aus dem `<ol>`-Default (`list-style:none` setzt\nihn zurück) — die Komponente muss dafür nichts weiter tun als `<ol\nclass=\"article-toc-list\">` zu rendern.",
729
729
  "docgen": {
730
730
  "$ref": "../services/core/docgen/seitenmuster-wissensbeitrag-inhaltsverzeichnis.json#/components/seitenmuster-wissensbeitrag-inhaltsverzeichnis"
731
731
  },
@@ -736,7 +736,7 @@
736
736
  "seitenmuster-wissensbeitrag-callout": {
737
737
  "id": "seitenmuster-wissensbeitrag-callout",
738
738
  "name": "ArticleCalloutComponent",
739
- "description": "ArticleCallout (`cds-article-callout`) — Wrapper um `.article-callout*` aus\ncss/components.css (css/components.css:1591–1600): der bereichsgetönte\nAside-Block für Praxis-Beispiele und „In der Realität“-Einschübe im Lauftext\neines Wissensbeitrags. Zwölftes Ticket der Seitenbausteine-Serie\n(„Artikel-Körper“). Ausgezählt: 5 reale Vorkommen, je eines pro Bereich in der\nDoku-Sektion (`docs/index.html:8086–8115`, co/ki/es/wo) plus eines in der realen\nBeispielseite Wissensbeitrag · KI (`docs/index.html:15110–15114`).\n\n**Element-Selektor, ADR-0008-Standardfall.** `.article-callout` sitzt in allen 5\nVorkommen als gewöhnlicher Block-Nachfahre — die 4 Doku-Boxen je in einem\nschlichten `<div>` innerhalb eines `display:flex;flex-direction:column`-Stapels\n(kein Stretch-Bedarf in Spaltenrichtung), die reale Instanz direkt in\n`.article-body`. Keines ist ein Grid-/Flex-Kind mit Streckungsbedarf, keines\nträgt eine `col-*`-Klasse, `.article-callout` kommt in keiner CSS-Regel mit\neinem Geschwister-Kombinator (`+`/`~`) vor, das Tag variiert nicht (immer\n`<aside>`). Keines der drei ADR-0008-Kriterien greift. Die einzige direkte\nKind-Regel von `.article-body` (`.article-body > p`, css/components.css:1566)\nzielt auf `p`, nicht auf `aside` — ein `<cds-article-callout>`-Host zwischen\n`.article-body` und dem `<aside>` bricht deshalb keine Selektorkette.\n\n**Projizierte Absätze bleiben direkte Kinder von `.article-callout`, kein\n`<div>` um `<ng-content>`** (Ticket-Vorgabe: `.article-callout > p` ist ein\nKindselektor, css/components.css:1600). `<ng-content>` selbst fügt kein\nDOM-Element ein — es verschiebt nur die vom Konsumenten geschriebenen\n`<p>`-Elemente an ihre Stelle direkt im `<aside>`, eine Ebene unter dem\n`<cds-article-callout>`-Host, aber ohne zusätzlichen Knoten dazwischen. Geprüft\nim laufenden Storybook (Story „Interaktiv“, Play-Funktion):\n`aside.article-callout > p` liefert sowohl die Eyebrow als auch jeden\nprojizierten Absatz, `:scope > *` zählt keine fremde Zwischenebene.\n\n**`eyebrow` ist Beiwerk (Default `''`), `area` ist Konfiguration mit\nverteidigbarer Vorgabe (`'co'`).** Die Basisregel `.article-callout` selbst\n(ohne `[data-area]`) rendert bereits exakt wie `[data-area=\"co\"]`\n(`background:var(--co-50)`, `border-left-color:var(--co-500)`, beide Werte\nidentisch an beiden Stellen, css/components.css:1591–1592) — `'co'` ist damit\nkeine erfundene Markenfarbe, sondern der reale CSS-Fallback ohne Attribut.\nAnders als bei `cds-pill` (dessen `.pill` ohne `data-area` gar keine Füllfarbe\nhat) gibt es hier keinen sinnvollen „kein Bereich“-Zustand.\n\n**Bekannter CSS-Befund, nicht im Wrapper geflickt (ADR-0001):**\n`.article-callout-eyebrow` (Spezifität 0,1,0) verliert gegen\n`.article-callout > p` (Spezifität 0,1,1, css/components.css:1600), weil die\nEyebrow selbst ein `<p>` UND ein direktes Kind von `.article-callout` ist.\nGemessen in rohem Markup ohne Angular (Playwright/Chromium,\n`getComputedStyle`): die Eyebrow rendert in JEDEM Bereich mit `font-size:16px`\nstatt `12px` und `margin-bottom:0` statt `4px`; die Akzentfarbe geht zusätzlich\nverloren, wenn kein bereichsspezifischer Override existiert (`co` und „kein\n`data-area`“ zeigen `--tx-primary` statt `--co-700`). Diese Komponente\nreproduziert exakt die Klassen und die Struktur des Mockups und zeigt deshalb\ndenselben, vorbestehenden Fehler wie rohes HTML — siehe\n`.scratch/angular-seitenbausteine/issues/22-css-luecke-callout-eyebrow-spezifitaet.md`.\n\n**`aria-labelledby` auf `<aside>`, sobald `eyebrow` gesetzt ist — Zusatz zum\nMockup, keine CSS-Änderung.** `<aside>` hat implizit die Landmark-Rolle\n`complementary`; ein Wissensbeitrag mit mehr als einem Callout hat damit\nmehrere gleichnamige Landmarks ohne zugänglichen Namen. Gemessen mit\naxe-core an rohem Markup ohne Angular (zwei `<aside class=\"article-callout\">`\nohne Auszeichnung): `landmark-unique` schlägt fehl, unabhängig von dieser\nKomponente — das Mockup selbst kennt diese Auszeichnung nicht (siehe\n`.scratch/angular-seitenbausteine/issues/23-doku-luecke-callout-landmark-label.md`).\nDie Eyebrow ist bereits ein prägnanter, vom Redakteur gepflegter Kurztext\ngenau für diesen Zweck (z. B. „In der Praxis“) — sie bekommt deshalb eine\ngenerierte `id`, auf die `aria-labelledby` zeigt, sobald sie existiert. Ohne\nEyebrow bleibt das `<aside>` unbenannt wie im Mockup: ein zugänglicher Name\nließe sich sonst nur aus dem projizierten Absatztext raten, und das wäre eine\nerfundene Bezeichnung, keine reale.",
739
+ "description": "ArticleCallout (`cds-article-callout`) — Wrapper um `.article-callout*` aus\ncss/components.css (css/components.css:1591–1600): der bereichsgetönte\nAside-Block für Praxis-Beispiele und „In der Realität“-Einschübe im Lauftext\neines Wissensbeitrags. Ausgezählt: 5 reale Vorkommen, je eines pro Bereich in der\nDoku-Sektion (`docs/index.html:8086–8115`, co/ki/es/wo) plus eines in der realen\nBeispielseite Wissensbeitrag · KI (`docs/index.html:15110–15114`).\n\n**Element-Selektor, ADR-0008-Standardfall.** `.article-callout` sitzt in allen 5\nVorkommen als gewöhnlicher Block-Nachfahre — die 4 Doku-Boxen je in einem\nschlichten `<div>` innerhalb eines `display:flex;flex-direction:column`-Stapels\n(kein Stretch-Bedarf in Spaltenrichtung), die reale Instanz direkt in\n`.article-body`. Keines ist ein Grid-/Flex-Kind mit Streckungsbedarf, keines\nträgt eine `col-*`-Klasse, `.article-callout` kommt in keiner CSS-Regel mit\neinem Geschwister-Kombinator (`+`/`~`) vor, das Tag variiert nicht (immer\n`<aside>`). Keines der drei ADR-0008-Kriterien greift. Die einzige direkte\nKind-Regel von `.article-body` (`.article-body > p`, css/components.css:1566)\nzielt auf `p`, nicht auf `aside` — ein `<cds-article-callout>`-Host zwischen\n`.article-body` und dem `<aside>` bricht deshalb keine Selektorkette.\n\n**Projizierte Absätze bleiben direkte Kinder von `.article-callout`, kein\n`<div>` um `<ng-content>`:** `.article-callout > p` ist ein\nKindselektor, css/components.css:1600. `<ng-content>` selbst fügt kein\nDOM-Element ein — es verschiebt nur die vom Konsumenten geschriebenen\n`<p>`-Elemente an ihre Stelle direkt im `<aside>`, eine Ebene unter dem\n`<cds-article-callout>`-Host, aber ohne zusätzlichen Knoten dazwischen. Geprüft\nim laufenden Storybook (Story „Interaktiv“, Play-Funktion):\n`aside.article-callout > p` liefert sowohl die Eyebrow als auch jeden\nprojizierten Absatz, `:scope > *` zählt keine fremde Zwischenebene.\n\n**`eyebrow` ist Beiwerk (Default `''`), `area` ist Konfiguration mit\nverteidigbarer Vorgabe (`'co'`).** Die Basisregel `.article-callout` selbst\n(ohne `[data-area]`) rendert bereits exakt wie `[data-area=\"co\"]`\n(`background:var(--co-50)`, `border-left-color:var(--co-500)`, beide Werte\nidentisch an beiden Stellen, css/components.css:1591–1592) — `'co'` ist damit\nkeine erfundene Markenfarbe, sondern der reale CSS-Fallback ohne Attribut.\nAnders als bei `cds-pill` (dessen `.pill` ohne `data-area` gar keine Füllfarbe\nhat) gibt es hier keinen sinnvollen „kein Bereich“-Zustand.\n\n**Bekannter CSS-Befund, nicht im Wrapper geflickt (ADR-0001):**\n`.article-callout-eyebrow` (Spezifität 0,1,0) verliert gegen\n`.article-callout > p` (Spezifität 0,1,1, css/components.css:1600), weil die\nEyebrow selbst ein `<p>` UND ein direktes Kind von `.article-callout` ist.\nGemessen in rohem Markup ohne Angular (Playwright/Chromium,\n`getComputedStyle`): die Eyebrow rendert in JEDEM Bereich mit `font-size:16px`\nstatt `12px` und `margin-bottom:0` statt `4px`; die Akzentfarbe geht zusätzlich\nverloren, wenn kein bereichsspezifischer Override existiert (`co` und „kein\n`data-area`“ zeigen `--tx-primary` statt `--co-700`). Diese Komponente\nreproduziert exakt die Klassen und die Struktur des Mockups und zeigt deshalb\ndenselben, vorbestehenden Fehler wie rohes HTML.\n\n**`aria-labelledby` auf `<aside>`, sobald `eyebrow` gesetzt ist — Zusatz zum\nMockup, keine CSS-Änderung.** `<aside>` hat implizit die Landmark-Rolle\n`complementary`; ein Wissensbeitrag mit mehr als einem Callout hat damit\nmehrere gleichnamige Landmarks ohne zugänglichen Namen. Gemessen mit\naxe-core an rohem Markup ohne Angular (zwei `<aside class=\"article-callout\">`\nohne Auszeichnung): `landmark-unique` schlägt fehl, unabhängig von dieser\nKomponente — das Mockup selbst kennt diese Auszeichnung nicht.\nDie Eyebrow ist bereits ein prägnanter, vom Redakteur gepflegter Kurztext\ngenau für diesen Zweck (z. B. „In der Praxis“) — sie bekommt deshalb eine\ngenerierte `id`, auf die `aria-labelledby` zeigt, sobald sie existiert. Ohne\nEyebrow bleibt das `<aside>` unbenannt wie im Mockup: ein zugänglicher Name\nließe sich sonst nur aus dem projizierten Absatztext raten, und das wäre eine\nerfundene Bezeichnung, keine reale.",
740
740
  "docgen": {
741
741
  "$ref": "../services/core/docgen/seitenmuster-wissensbeitrag-callout.json#/components/seitenmuster-wissensbeitrag-callout"
742
742
  },
@@ -747,7 +747,7 @@
747
747
  "seitenmuster-wissensbeitrag-figure": {
748
748
  "id": "seitenmuster-wissensbeitrag-figure",
749
749
  "name": "ArticleFigureComponent",
750
- "description": "ArticleFigure (`cds-article-figure`) — Wrapper um `.article-figure`/\n`.article-figcaption` aus css/components.css (css/components.css:1573–1575):\ndas redaktionelle Inline-Bild mit optionaler Bildunterschrift im Lauftext eines\nWissensbeitrags. Zwölftes Ticket der Seitenbausteine-Serie („Artikel-Körper“).\nAusgezählt: 3 reale Vorkommen — Doku-Demo mit Caption\n(`docs/index.html:8028–8031`), reales Body-Bild mit Caption in der Beispielseite\nWissensbeitrag · KI (`docs/index.html:15115–15118`, identischer Bild- und\nCaption-Text wie die Doku-Demo) und ein dritter, captionsloser Sonderfall direkt\nunter dem Article Header derselben Beispielseite (`docs/index.html:15040–15042`,\ndazu unten mehr).\n\n**Element-Selektor, ADR-0008-Standardfall.** In allen 3 Vorkommen sitzt\n`.article-figure` als gewöhnlicher Block-Nachfahre (zwei in `.article-body`,\neines direkt in einer eigenen `.ep-section`) — kein Grid-/Flex-Kind, keine\n`col-*`-Klasse vom Konsumenten, kein Tag-Wechsel (immer `<figure>`), kein\nVorkommen von `.article-figure` mit einem Geschwister-Kombinator in\ncss/components.css. Die einzige direkte Kind-Regel von `.article-body`\n(`.article-body > p`, css/components.css:1566) zielt auf `p`, nicht auf\n`figure` — ein `<cds-article-figure>`-Host zwischen `.article-body` und dem\n`<figure>` bricht deshalb keine Selektorkette.\n\n**`loading=\"lazy\"` ist fest verdrahtet, kein Input — bewusst NUR der\nIn-Article-Fall, nicht der dritte Mockup-Beleg.** Ausgezählt tragen 2 der 3\nVorkommen `loading=\"lazy\"` (die beiden Bilder MIT Caption, mittig im\nLauftext, „below the fold“ — genau der in `wissensbeitrag.mdx`, Abschnitt\n„In-Article Figure“, dokumentierte Dos-Punkt). Das dritte Vorkommen\n(`docs/index.html:15040–15042`) trägt stattdessen `loading=\"eager\"\nfetchpriority=\"high\"` UND hat keine Caption — es ist das erste Bild direkt\nunter dem Article Header (Kommentar im Mockup: „Hero-Bild auf Body-Breite …\nidentisches Bild wie Vorschaubild für Wiedererkennung“), sitzt außerhalb von\n`.article-body` und bekommt seine 720-px-Breite über ein\nInline-`style=\"max-width:720px;margin:0 auto\"` am Konsumenten, nicht über die\nKomponente (Randbedingung 6 der Spec: Mockup-Inline-Styles wandern nicht in\nden Wrapper). Die Ticket-API sieht dafür kein `loading`/`fetchpriority`-Input\nvor; dieses Bauteil deckt deshalb den (häufigeren, dokumentierten) In-Body-Fall\nab. Für den Lead-Bild-Sonderfall bleibt `<figure class=\"article-figure\">` roh\nschreibbar oder — sofern die Semantik passt — `cds-hero-image` (Ticket 02) zu\nprüfen.\n\n**`alt` und `caption` sind bewusst getrennte Pflicht-/Beiwerk-Inputs, keine\nPrüfung auf inhaltliche Überschneidung.** `wissensbeitrag.mdx` dokumentiert die\nbeiden Rollen ausdrücklich als unterschiedliche Jobs\n(Dont: „Alt-Text und Caption inhaltlich identisch, Screenreader liest\ndoppelt“) — Vorgabe an den Konsumenten, nicht etwas, das eine Wrapper-Komponente\nzur Laufzeit sinnvoll erzwingen könnte.",
750
+ "description": "ArticleFigure (`cds-article-figure`) — Wrapper um `.article-figure`/\n`.article-figcaption` aus css/components.css (css/components.css:1573–1575):\ndas redaktionelle Inline-Bild mit optionaler Bildunterschrift im Lauftext eines\nWissensbeitrags. Ausgezählt: 3 reale Vorkommen — Doku-Demo mit Caption\n(`docs/index.html:8028–8031`), reales Body-Bild mit Caption in der Beispielseite\nWissensbeitrag · KI (`docs/index.html:15115–15118`, identischer Bild- und\nCaption-Text wie die Doku-Demo) und ein dritter, captionsloser Sonderfall direkt\nunter dem Article Header derselben Beispielseite (`docs/index.html:15040–15042`,\ndazu unten mehr).\n\n**Element-Selektor, ADR-0008-Standardfall.** In allen 3 Vorkommen sitzt\n`.article-figure` als gewöhnlicher Block-Nachfahre (zwei in `.article-body`,\neines direkt in einer eigenen `.ep-section`) — kein Grid-/Flex-Kind, keine\n`col-*`-Klasse vom Konsumenten, kein Tag-Wechsel (immer `<figure>`), kein\nVorkommen von `.article-figure` mit einem Geschwister-Kombinator in\ncss/components.css. Die einzige direkte Kind-Regel von `.article-body`\n(`.article-body > p`, css/components.css:1566) zielt auf `p`, nicht auf\n`figure` — ein `<cds-article-figure>`-Host zwischen `.article-body` und dem\n`<figure>` bricht deshalb keine Selektorkette.\n\n**`loading=\"lazy\"` ist fest verdrahtet, kein Input — bewusst NUR der\nIn-Article-Fall, nicht der dritte Mockup-Beleg.** Ausgezählt tragen 2 der 3\nVorkommen `loading=\"lazy\"` (die beiden Bilder MIT Caption, mittig im\nLauftext, „below the fold“ — genau der in `wissensbeitrag.mdx`, Abschnitt\n„In-Article Figure“, dokumentierte Dos-Punkt). Das dritte Vorkommen\n(`docs/index.html:15040–15042`) trägt stattdessen `loading=\"eager\"\nfetchpriority=\"high\"` UND hat keine Caption — es ist das erste Bild direkt\nunter dem Article Header (Kommentar im Mockup: „Hero-Bild auf Body-Breite …\nidentisches Bild wie Vorschaubild für Wiedererkennung“), sitzt außerhalb von\n`.article-body` und bekommt seine 720-px-Breite über ein\nInline-`style=\"max-width:720px;margin:0 auto\"` am Konsumenten, nicht über die\nKomponente (Mockup-Inline-Styles wandern bewusst nicht in den Wrapper). Die\nAPI sieht dafür kein `loading`/`fetchpriority`-Input\nvor; dieses Bauteil deckt deshalb den (häufigeren, dokumentierten) In-Body-Fall\nab. Für den Lead-Bild-Sonderfall bleibt `<figure class=\"article-figure\">` roh\nschreibbar oder — sofern die Semantik passt — `cds-hero-image` zu\nprüfen.\n\n**`alt` und `caption` sind bewusst getrennte Pflicht-/Beiwerk-Inputs, keine\nPrüfung auf inhaltliche Überschneidung.** `wissensbeitrag.mdx` dokumentiert die\nbeiden Rollen ausdrücklich als unterschiedliche Jobs\n(Dont: „Alt-Text und Caption inhaltlich identisch, Screenreader liest\ndoppelt“) — Vorgabe an den Konsumenten, nicht etwas, das eine Wrapper-Komponente\nzur Laufzeit sinnvoll erzwingen könnte.",
751
751
  "docgen": {
752
752
  "$ref": "../services/core/docgen/seitenmuster-wissensbeitrag-figure.json#/components/seitenmuster-wissensbeitrag-figure"
753
753
  },
@@ -758,7 +758,7 @@
758
758
  "seitenmuster-wissensbeitrag-pull-quote": {
759
759
  "id": "seitenmuster-wissensbeitrag-pull-quote",
760
760
  "name": "ArticlePullquoteComponent",
761
- "description": "ArticlePullquote (`cds-article-pullquote`) — Wrapper um `.article-pullquote` aus\ncss/components.css (css/components.css:1608–1612): die typografische\nHervorhebung eines Satzes aus dem eigenen Lauftext eines Wissensbeitrags.\nZwölftes Ticket der Seitenbausteine-Serie („Artikel-Körper“). Ausgezählt: 2\nreale Vorkommen, wortgleich, beide `data-area=\"ki\"` — Doku-Demo\n(`docs/index.html:8136`) und reale Beispielseite Wissensbeitrag · KI\n(`docs/index.html:15075`).\n\n**Nicht `cds-blockquote` (`BlockquoteComponent`, `.bq`) — Ticket-Vorgabe, im\nJSDoc beider Bauteile wechselseitig abgegrenzt.** Pull-Quote und Blockquote\nteilen die typografische Familie (Serif, bereichsgefärbter Linksakzent in\n`{area}-500`), aber nicht den Zweck, dokumentiert in `wissensbeitrag.mdx`,\nAbschnitt „Pull-Quote gegen Blockquote“:\n\n| | Pull-Quote (dieses Bauteil) | Blockquote (`cds-blockquote`) |\n|---|---|---|\n| Wessen Stimme? | Der Beitrag zitiert sich selbst — ein Schlüsselsatz aus dem eigenen Lauftext | Eine dritte, benannte Person (Kund:in, Forschungsstimme, Branchen-Statement) |\n| Attribution | Keine (`quote` ist der einzige Input) | Pflicht (`name`, `roleLabel`) |\n| Hintergrund/Icon | Keiner — `quotes:none`, kein Quote-Icon, bewusst „offen“ statt umrahmt | Getönte Akzent-Box (`--bq-bg`) mit Quote-Icon (`.bq-icon`) |\n| Tag | `<blockquote class=\"article-pullquote\">` direkt, kein umschließendes `<figure>` | `<figure class=\"bq\">` außen, `<blockquote>` (ohne eigene Klasse) innen für den Zitattext |\n\nEin Self-Quote im eigenen Beitrag (Blockquote-Optik auf einem Satz des\nAutors) wirkt sonst absurd — der Autor zitiert die Person, die den Beitrag\nschreibt. Wer eine externe Stimme mit Namen und Rolle zeigen will, verwendet\n`cds-blockquote`.\n\n**`quote` enthält die deutschen Anführungszeichen bereits als Teil des Texts,\ndie Komponente ergänzt keine.** `.article-pullquote{quotes:none}`\n(css/components.css:1608) — es gibt keine `content:open-quote`/`close-quote`-Regel\nin der CSS-Schicht, weder hier noch bei `.bq blockquote` oder\n`.testimonial blockquote`. Ausgezählt stehen die Anführungszeichen in BEIDEN\nrealen Vorkommen als literale Zeichen im Text\n(„KI scheitert selten an der Technologie. …“), nicht CSS-generiert. Wie bei\n`cds-compare`/`cds-table` ist Copy-Text Sache des Konsumenten (ADR-0007 §2) —\ndie Story liefert deshalb `quote` bereits mit „…“ (CONTRIBUTING.md §6), die\nKomponente rendert den String unverändert, ohne eigene Anführungszeichen zu\nerfinden.\n\n**Element-Selektor, ADR-0008-Standardfall.** `.article-pullquote` sitzt in\nbeiden Vorkommen als gewöhnlicher Block-Nachfahre in `.article-body` — kein\nGrid-/Flex-Kind, keine `col-*`-Klasse vom Konsumenten, kein\nGeschwister-Kombinator in css/components.css, kein Tag-Wechsel (immer\n`<blockquote>`). Die einzige direkte Kind-Regel von `.article-body`\n(`.article-body > p`, css/components.css:1566) zielt auf `p`, nicht auf\n`blockquote` — ein `<cds-article-pullquote>`-Host zwischen `.article-body` und\ndem `<blockquote>` bricht deshalb keine Selektorkette.\n\n**`area` ist Konfiguration mit verteidigbarer Vorgabe (`'co'`), keine\nerfundene Bereichsfarbe.** Die Basisregel `.article-pullquote` selbst (ohne\n`[data-area]`) setzt bereits exakt denselben Wert wie `[data-area=\"co\"]`\n(`border-left-color:var(--co-500)`, css/components.css:1608–1609) — `'co'` ist\ndamit der reale CSS-Fallback ohne Attribut, nicht geraten.",
761
+ "description": "ArticlePullquote (`cds-article-pullquote`) — Wrapper um `.article-pullquote` aus\ncss/components.css (css/components.css:1608–1612): die typografische\nHervorhebung eines Satzes aus dem eigenen Lauftext eines Wissensbeitrags.\nAusgezählt: 2 reale Vorkommen, wortgleich, beide `data-area=\"ki\"` — Doku-Demo\n(`docs/index.html:8136`) und reale Beispielseite Wissensbeitrag · KI\n(`docs/index.html:15075`).\n\n**Nicht `cds-blockquote` (`BlockquoteComponent`, `.bq`) — im\nJSDoc beider Bauteile wechselseitig abgegrenzt.** Pull-Quote und Blockquote\nteilen die typografische Familie (Serif, bereichsgefärbter Linksakzent in\n`{area}-500`), aber nicht den Zweck, dokumentiert in `wissensbeitrag.mdx`,\nAbschnitt „Pull-Quote gegen Blockquote“:\n\n| | Pull-Quote (dieses Bauteil) | Blockquote (`cds-blockquote`) |\n|---|---|---|\n| Wessen Stimme? | Der Beitrag zitiert sich selbst — ein Schlüsselsatz aus dem eigenen Lauftext | Eine dritte, benannte Person (Kund:in, Forschungsstimme, Branchen-Statement) |\n| Attribution | Keine (`quote` ist der einzige Input) | Pflicht (`name`, `roleLabel`) |\n| Hintergrund/Icon | Keiner — `quotes:none`, kein Quote-Icon, bewusst „offen“ statt umrahmt | Getönte Akzent-Box (`--bq-bg`) mit Quote-Icon (`.bq-icon`) |\n| Tag | `<blockquote class=\"article-pullquote\">` direkt, kein umschließendes `<figure>` | `<figure class=\"bq\">` außen, `<blockquote>` (ohne eigene Klasse) innen für den Zitattext |\n\nEin Self-Quote im eigenen Beitrag (Blockquote-Optik auf einem Satz des\nAutors) wirkt sonst absurd — der Autor zitiert die Person, die den Beitrag\nschreibt. Wer eine externe Stimme mit Namen und Rolle zeigen will, verwendet\n`cds-blockquote`.\n\n**`quote` enthält die deutschen Anführungszeichen bereits als Teil des Texts,\ndie Komponente ergänzt keine.** `.article-pullquote{quotes:none}`\n(css/components.css:1608) — es gibt keine `content:open-quote`/`close-quote`-Regel\nin der CSS-Schicht, weder hier noch bei `.bq blockquote` oder\n`.testimonial blockquote`. Ausgezählt stehen die Anführungszeichen in BEIDEN\nrealen Vorkommen als literale Zeichen im Text\n(„KI scheitert selten an der Technologie. …“), nicht CSS-generiert. Wie bei\n`cds-compare`/`cds-table` ist Copy-Text Sache des Konsumenten (ADR-0007 §2) —\ndie Story liefert deshalb `quote` bereits mit „…“ (CONTRIBUTING.md §6), die\nKomponente rendert den String unverändert, ohne eigene Anführungszeichen zu\nerfinden.\n\n**Element-Selektor, ADR-0008-Standardfall.** `.article-pullquote` sitzt in\nbeiden Vorkommen als gewöhnlicher Block-Nachfahre in `.article-body` — kein\nGrid-/Flex-Kind, keine `col-*`-Klasse vom Konsumenten, kein\nGeschwister-Kombinator in css/components.css, kein Tag-Wechsel (immer\n`<blockquote>`). Die einzige direkte Kind-Regel von `.article-body`\n(`.article-body > p`, css/components.css:1566) zielt auf `p`, nicht auf\n`blockquote` — ein `<cds-article-pullquote>`-Host zwischen `.article-body` und\ndem `<blockquote>` bricht deshalb keine Selektorkette.\n\n**`area` ist Konfiguration mit verteidigbarer Vorgabe (`'co'`), keine\nerfundene Bereichsfarbe.** Die Basisregel `.article-pullquote` selbst (ohne\n`[data-area]`) setzt bereits exakt denselben Wert wie `[data-area=\"co\"]`\n(`border-left-color:var(--co-500)`, css/components.css:1608–1609) — `'co'` ist\ndamit der reale CSS-Fallback ohne Attribut, nicht geraten.",
762
762
  "docgen": {
763
763
  "$ref": "../services/core/docgen/seitenmuster-wissensbeitrag-pull-quote.json#/components/seitenmuster-wissensbeitrag-pull-quote"
764
764
  },
@@ -780,7 +780,7 @@
780
780
  "seitenmuster-wissensbeitrag-author-card": {
781
781
  "id": "seitenmuster-wissensbeitrag-author-card",
782
782
  "name": "AuthorCardComponent",
783
- "description": "AuthorCard (`div[cdsAuthorCard]`) — Wrapper um `.author-card` aus css/components.css\n(css/components.css:1602–1606): der Avatar-plus-Bio-Strip am Ende eines\nWissensbeitrags, einzeln oder mehrfach in `cds-author-card-group`. Dreizehntes und\nletztes Ticket der Seitenbausteine-Serie. Ausgezählt: 17 reale Vorkommen in\n`docs/index.html` — 4 einzelstehend (2 Doku-Demos `docs/index.html:8193,8207`, 1\nArtikel-Demo `8424`, 1 reale Beispielseite `15179`) und 13 in fünf Gruppen (siehe\n`AuthorCardGroupComponent`).\n\n**Attributselektor (ADR-0008), gemessen im `is-grid`-Fall statt nur hergeleitet.**\n`.author-card-group.is-grid` (css/components.css:1555) ist `display:grid` ohne\neigenes `align-items` — der CSS-Default `stretch` gleicht die Kartenhöhen einer\nReihe deshalb NUR aus, wenn `.author-card` selbst das Grid-Kind ist. Spike-Story\n(zwei Karten, 600-px-Raster, kurze gegen deutlich längere Bio, seither wieder\ngelöscht) hat beide Varianten nebeneinander gebaut und mit `getBoundingClientRect()`\ngemessen:\n\n| Variante | Grid-Kind (Tag) | `.author-card`-Höhen | Bio-Rect (kurz / lang) |\n|---|---|---|---|\n| Element-Selektor `<cds-author-card-naive>` | `CDS-AUTHOR-CARD-NAIVE`, 204/204 px (gestreckt) | **84 / 204 px** (ungleich) | 24×73 / 144×200 px |\n| Attributselektor `div[cdsAuthorCardSpike]` | `DIV.author-card`, 204/204 px | **204 / 204 px** (gleich) | — |\n\nBeim Element-Selektor wird der UNSICHTBARE `<cds-author-card-naive>`-Host auf 204 px\ngestreckt, die sichtbare `.author-card`-Box eine Ebene darunter bleibt bei ihrer\nInhaltshöhe (84 px) — exakt der Fehler aus ADR-0008 Fall 1. Mit dem Attributselektor\nIST `.author-card` das Grid-Kind, `align-items:stretch` trifft direkt zu.\n\n**Ehrlicher Zusatz, anders als bei `cds-icon-card`: die Baseline-Screenshots beider\nSpike-Varianten waren byte-identisch** (`sha1` gleich). `.author-card` trägt (anders\nals `.ep-card`) weder Hintergrund noch Rahmen noch Schatten — der zusätzliche,\nunsichtbare Leerraum unter der kurzen Bio malt in keiner der beiden Varianten ein\nPixel. Der Fehler ist damit heute rein strukturell (falsches Boxmodell, `getBoundingClientRect()`\nauf `.author-card` lügt), nicht optisch sichtbar. Er wird real, sobald `.author-card`\nkünftig einen Rahmen/Hintergrund/Hover-Zustand bekommt, und er verfälscht schon\nheute jede Messung/Positionierung, die sich auf die tatsächliche Kartenhöhe verlässt\n(u. a. die Story „Als Raster“ unten, die genau das pinnt). Das ADR-0008-Kriterium\nist strukturell formuliert („die Stelle im DOM einnehmen, die es ohne Angular\neinnähme“), nicht optisch — der Attributselektor bleibt deshalb die richtige Wahl,\nobwohl der Effekt heute unsichtbar ist.\n\n**`area` ist bewusst NICHT Teil der API — CSS-Befund, nicht erfundene Konfiguration.**\nAlle 17 realen `.author-card`-Vorkommen tragen `data-area`, und sowohl\n`docs/index.html:8185` als auch `wissensbeitrag.mdx:199` behaupten wörtlich, das\nfärbe „nur den Avatar“. Ausgezählt gibt es aber in `css/components.css` (Light UND\nDark) KEINE einzige `.author-card[data-area=\"…\"]`-Regel — weder direkt noch über\neinen Nachfahren-Selektor auf `.article-avatar`, dessen eigene\n`[data-area]`-Färbung (css/components.css:1523–1526) ein Element-Selektor auf dem\nAvatar selbst ist, unabhängig vom Vorfahren. `data-area` auf `.author-card` ist\ndamit, gegen die Doku-Aussage, komplett wirkungslos — die Bereichsfarbe kommt\nausschließlich vom `area`-Input, den der Konsument direkt an das projizierte\n`[cdsAvatar]` bindet (siehe unten). Ein `area`-Input, der hier nur `data-area` ohne\njede CSS-Wirkung setzt, wäre eine erfundene Konfiguration ohne Gegenwert — Befund\ngemeldet, nicht im Wrapper geflickt (ADR-0001):\n`.scratch/angular-seitenbausteine/issues/24-css-luecke-author-card-data-area.md`.\n\n**Avatar projiziert über `[cdsAvatar]`, wie bei `cds-article-header`.** Der\nKonsument setzt Größe (`size=\"lg\"`, css/components.css:1527) und Bereich\n(`area=\"…\"`) direkt am projizierten `<div cdsAvatar>` — diese Komponente reicht\nnichts durch, exakt die Trennung, die `cds-article-header` bereits etabliert\n(siehe dessen Klassendoku, Abschnitt „Avatar projiziert über `[cdsAvatar]`“).\n\n**Eyebrow als `<h3>`, nur wenn gesetzt** (Beiwerk, Default `''`) — deckungsgleich\nmit allen 4 einzelstehenden realen Vorkommen: 2 tragen `<h3 class=\"author-card-eyebrow\">`\n(`docs/index.html:8196,8427`, „Über den Autor“/„Über die Autorin“, Ticket-Vorgabe:\neine Ebene unter dem `<h2>` des Article-Body), die anderen 2 (Doku-Demos ohne\numgebende Artikelstruktur) haben gar keinen. Innerhalb einer Gruppe trägt laut\n`wissensbeitrag.mdx` („Eyebrow-Konvention“) keine einzelne Karte mehr einen eigenen\nEyebrow — die Gruppen-Überschrift ersetzt sie (siehe `AuthorCardGroupComponent`);\nKonsumenten lassen `eyebrow` dafür einfach leer.\n\n**`roleLabel`, NICHT `role` wie im Ticket-Text — gemessene Abweichung, kein\nFreihand-Entscheid.** Ein erster Entwurf folgte der Ticket-API wörtlich\n(`role`-Input, mit `role=\"…\"` als schlichtem Attribut in den Stories gesetzt, wie\n`name`/`bio`/`eyebrow`). Storybooks eingebauter a11y-Check (`aria-roles`) schlug\ndarauf für JEDE Story mit echtem Rollentext fehl: `role` ist ein globales\nHTML-/ARIA-Attribut, und ein Angular-Input MIT DIESEM NAMEN wird bei einem\nungebundenen `role=\"Senior AI Engineer …\"` zwar als Startwert an den Input\ndurchgereicht, bleibt aber ZUSÄTZLICH als echtes `role`-Attribut im DOM stehen —\naxe meldete wörtlich „Roles must be one of the valid ARIA roles: Senior, AI,\nEngineer, …“, weil der Freitext als ARIA-Rolle interpretiert wird. Exakt dieselbe\nKollision umgehen `BlockquoteComponent`, `TestimonialComponent` und\n`TeamVoiceComponent` bereits mit `roleLabel` statt `role`\n(`grep -rn \"readonly role\" angular-lib/projects/design-system-angular/src/lib/`\nzeigt nur diese drei). Diese Komponente folgt demselben, bereits etablierten\nMuster statt die Kollision ein viertes Mal zu wiederholen — CSS-Klasse bleibt\nunverändert `.author-card-role`, nur der Input-Name weicht vom Ticket-Text ab.",
783
+ "description": "AuthorCard (`div[cdsAuthorCard]`) — Wrapper um `.author-card` aus css/components.css\n(css/components.css:1602–1606): der Avatar-plus-Bio-Strip am Ende eines\nWissensbeitrags, einzeln oder mehrfach in `cds-author-card-group`. Ausgezählt: 17 reale Vorkommen in\n`docs/index.html` — 4 einzelstehend (2 Doku-Demos `docs/index.html:8193,8207`, 1\nArtikel-Demo `8424`, 1 reale Beispielseite `15179`) und 13 in fünf Gruppen (siehe\n`AuthorCardGroupComponent`).\n\n**Attributselektor (ADR-0008), gemessen im `is-grid`-Fall statt nur hergeleitet.**\n`.author-card-group.is-grid` (css/components.css:1555) ist `display:grid` ohne\neigenes `align-items` — der CSS-Default `stretch` gleicht die Kartenhöhen einer\nReihe deshalb NUR aus, wenn `.author-card` selbst das Grid-Kind ist. Spike-Story\n(zwei Karten, 600-px-Raster, kurze gegen deutlich längere Bio, seither wieder\ngelöscht) hat beide Varianten nebeneinander gebaut und mit `getBoundingClientRect()`\ngemessen:\n\n| Variante | Grid-Kind (Tag) | `.author-card`-Höhen | Bio-Rect (kurz / lang) |\n|---|---|---|---|\n| Element-Selektor `<cds-author-card-naive>` | `CDS-AUTHOR-CARD-NAIVE`, 204/204 px (gestreckt) | **84 / 204 px** (ungleich) | 24×73 / 144×200 px |\n| Attributselektor `div[cdsAuthorCardSpike]` | `DIV.author-card`, 204/204 px | **204 / 204 px** (gleich) | — |\n\nBeim Element-Selektor wird der UNSICHTBARE `<cds-author-card-naive>`-Host auf 204 px\ngestreckt, die sichtbare `.author-card`-Box eine Ebene darunter bleibt bei ihrer\nInhaltshöhe (84 px) — exakt der Fehler aus ADR-0008 Fall 1. Mit dem Attributselektor\nIST `.author-card` das Grid-Kind, `align-items:stretch` trifft direkt zu.\n\n**Ehrlicher Zusatz, anders als bei `cds-icon-card`: die Baseline-Screenshots beider\nSpike-Varianten waren byte-identisch** (`sha1` gleich). `.author-card` trägt (anders\nals `.ep-card`) weder Hintergrund noch Rahmen noch Schatten — der zusätzliche,\nunsichtbare Leerraum unter der kurzen Bio malt in keiner der beiden Varianten ein\nPixel. Der Fehler ist damit heute rein strukturell (falsches Boxmodell, `getBoundingClientRect()`\nauf `.author-card` lügt), nicht optisch sichtbar. Er wird real, sobald `.author-card`\nkünftig einen Rahmen/Hintergrund/Hover-Zustand bekommt, und er verfälscht schon\nheute jede Messung/Positionierung, die sich auf die tatsächliche Kartenhöhe verlässt\n(u. a. die Story „Als Raster“ unten, die genau das pinnt). Das ADR-0008-Kriterium\nist strukturell formuliert („die Stelle im DOM einnehmen, die es ohne Angular\neinnähme“), nicht optisch — der Attributselektor bleibt deshalb die richtige Wahl,\nobwohl der Effekt heute unsichtbar ist.\n\n**`area` ist bewusst NICHT Teil der API — CSS-Befund, nicht erfundene Konfiguration.**\nAlle 17 realen `.author-card`-Vorkommen tragen `data-area`, und sowohl\n`docs/index.html:8185` als auch `wissensbeitrag.mdx:199` behaupten wörtlich, das\nfärbe „nur den Avatar“. Ausgezählt gibt es aber in `css/components.css` (Light UND\nDark) KEINE einzige `.author-card[data-area=\"…\"]`-Regel — weder direkt noch über\neinen Nachfahren-Selektor auf `.article-avatar`, dessen eigene\n`[data-area]`-Färbung (css/components.css:1523–1526) ein Element-Selektor auf dem\nAvatar selbst ist, unabhängig vom Vorfahren. `data-area` auf `.author-card` ist\ndamit, gegen die Doku-Aussage, komplett wirkungslos — die Bereichsfarbe kommt\nausschließlich vom `area`-Input, den der Konsument direkt an das projizierte\n`[cdsAvatar]` bindet (siehe unten). Ein `area`-Input, der hier nur `data-area` ohne\njede CSS-Wirkung setzt, wäre eine erfundene Konfiguration ohne Gegenwert — Befund\ngemeldet, nicht im Wrapper geflickt (ADR-0001).\n\n**Avatar projiziert über `[cdsAvatar]`, wie bei `cds-article-header`.** Der\nKonsument setzt Größe (`size=\"lg\"`, css/components.css:1527) und Bereich\n(`area=\"…\"`) direkt am projizierten `<div cdsAvatar>` — diese Komponente reicht\nnichts durch, exakt die Trennung, die `cds-article-header` bereits etabliert\n(siehe dessen Klassendoku, Abschnitt „Avatar projiziert über `[cdsAvatar]`“).\n\n**Eyebrow als `<h3>`, nur wenn gesetzt** (Beiwerk, Default `''`) — deckungsgleich\nmit allen 4 einzelstehenden realen Vorkommen: 2 tragen `<h3 class=\"author-card-eyebrow\">`\n(`docs/index.html:8196,8427`, „Über den Autor“/„Über die Autorin“:\neine Ebene unter dem `<h2>` des Article-Body), die anderen 2 (Doku-Demos ohne\numgebende Artikelstruktur) haben gar keinen. Innerhalb einer Gruppe trägt laut\n`wissensbeitrag.mdx` („Eyebrow-Konvention“) keine einzelne Karte mehr einen eigenen\nEyebrow — die Gruppen-Überschrift ersetzt sie (siehe `AuthorCardGroupComponent`);\nKonsumenten lassen `eyebrow` dafür einfach leer.\n\n**`roleLabel`, NICHT `role` wie in einem ersten Entwurf — gemessene Abweichung, kein\nFreihand-Entscheid.** Der erste Entwurf setzte `role` wörtlich um\n(`role`-Input, mit `role=\"…\"` als schlichtem Attribut in den Stories gesetzt, wie\n`name`/`bio`/`eyebrow`). Storybooks eingebauter a11y-Check (`aria-roles`) schlug\ndarauf für JEDE Story mit echtem Rollentext fehl: `role` ist ein globales\nHTML-/ARIA-Attribut, und ein Angular-Input MIT DIESEM NAMEN wird bei einem\nungebundenen `role=\"Senior AI Engineer …\"` zwar als Startwert an den Input\ndurchgereicht, bleibt aber ZUSÄTZLICH als echtes `role`-Attribut im DOM stehen —\naxe meldete wörtlich „Roles must be one of the valid ARIA roles: Senior, AI,\nEngineer, …“, weil der Freitext als ARIA-Rolle interpretiert wird. Exakt dieselbe\nKollision umgehen `BlockquoteComponent`, `TestimonialComponent` und\n`TeamVoiceComponent` bereits mit `roleLabel` statt `role`\n(`grep -rn \"readonly role\" angular-lib/projects/design-system-angular/src/lib/`\nzeigt nur diese drei). Diese Komponente folgt demselben, bereits etablierten\nMuster statt die Kollision ein viertes Mal zu wiederholen — CSS-Klasse bleibt\nunverändert `.author-card-role`, nur der Input-Name weicht vom ursprünglichen Entwurf ab.",
784
784
  "docgen": {
785
785
  "$ref": "../services/core/docgen/seitenmuster-wissensbeitrag-author-card.json#/components/seitenmuster-wissensbeitrag-author-card"
786
786
  },
@@ -791,7 +791,7 @@
791
791
  "seitenmuster-seminar-·-training-fakten-liste": {
792
792
  "id": "seitenmuster-seminar-·-training-fakten-liste",
793
793
  "name": "FactsComponent",
794
- "description": "Facts (`cds-facts`) — Wrapper um `.ep-facts` aus css/components.css\n(css/components.css:1261–1272): die Definitionsliste für die harten Eckdaten\neines Angebots (Termin, Dauer, Ort, Preis), einspaltig mit Haarlinie zwischen\nden Paaren oder als `.is-grid` zweispaltig für Kästen, die neben Inhalt stehen.\nBewusst ohne eigenen Rahmen und ohne Fläche, sie zieht in einen vorhandenen\nContainer ein (Angebots-Box, Sticky-Sidebar) — Doku-Sektion\n`docs/index.html:9069` (`gt-seminar-fakten`), Beispielseiten `docs/index.html:\n9076–9082`, `13718–13726`, `16293–16300`.\n\n**Element-Selektor, kein Attribut (ADR-0008-Standardfall).** Ausgezählt: keines\nder 3 `.ep-facts`-Vorkommen in `docs/index.html` sitzt in einem `.layout-grid`\nmit `col-*`, keines wird von einem Grid-/Flex-Elternteil in der Höhe gedehnt\n(der Flex-Fall in Zeile 16290 ist `flex-direction:column`, dort füllen Block-\nElemente die Breite ihres Containers unabhängig von einer zusätzlichen\nWrapper-Ebene — anders als bei `.ep-card`/ADR-0008 Fall 1 geht es dort um Höhe,\ndie durch Block-Layout NICHT automatisch nach unten durchgereicht wird). Das\nTag variiert nicht (immer `<dl>`). Analog zu `cds-faq` (`faq.component.ts`, das\nintern `<details>` in einem `<div class=\"ep-faq\">` rendert) trägt hier die\nINNERE `<dl class=\"ep-facts\">` die CSS-Klasse, nicht der `<cds-facts>`-Host: die\nSemantik einer Definitionsliste hängt am `<dl>`-Tag selbst, ein Custom-Element\nkann dieses Tag nicht annehmen. Da `.ep-facts > div + div` ausschließlich\nNachfahren INNERHALB der eigenen Vorlage anspricht, bricht eine zusätzliche\nHost-Ebene darüber nichts — anders als bei `cds-section` (Fall 2) hängt hier\nkeine von außen gesetzte Fläche oder ein von außen adressierter Nachfahre an\ndieser Tiefe.\n\n**`<dl>`/`<div>`/`<dt>`/`<dd>` exakt wie vom CSS erwartet.** `.ep-facts > div +\ndiv` (css/components.css:1262) setzt Haarlinie und Abstand ab dem ZWEITEN Paar;\nohne das umschließende `<div>` pro Paar griffe der Selektor nicht. Jedes\n`items()`-Element wird deshalb zu genau einem `<div><dt>…</dt><dd>…</dd></div>`.\n\n**`area` färbt `<dt>` über `.t-{area}`, nicht über `data-area`.** Anders als bei\n`cds-feature`/`cds-icon-card` kennt `.ep-facts` selbst kein `[data-area]`; der\nCSS-Kommentar zu dieser Klasse (`css/components.css:1259`) benennt\nstattdessen ausdrücklich „Bereichston der Labels über `.t-XX` im Markup\n(dark-safe), nie als Inline-Farbe“ als sanktionierten Weg. Ausgezählt: alle 3\n`.ep-facts`-Vorkommen in `docs/index.html` setzen `.t-wo` auf JEDEM `<dt>`\nderselben Liste (nie gemischt, nie eine andere Bereichsfarbe) — ein optionaler\n`area`-Input, der `.t-{area}` einheitlich auf alle `<dt>` der Instanz anwendet,\nbildet dieses Muster nach, ohne einen Farbwert zu erfinden (`.t-XX` ist eine\nbestehende, bereits kontrast- und dark-mode-geprüfte Utility, `css/components.css\n:638–642`). Ungesetzt (Default) bleibt `<dt>` ohne zusätzliche Klasse, erbt also\ndie umgebende Textfarbe — das deckt Kontexte ohne Bereichsbindung ab.\n\n**Über die Ticket-API hinaus:** `area` stand nicht in der ursprünglichen\nAPI-Skizze von Ticket 08. Ergänzt, weil ohne ihn KEINES der 3 realen\n`.ep-facts`-Vorkommen originalgetreu nachgebaut werden könnte — anders als bei\n`cds-tier`s `area` (dort deckt die Einschränkung eine tatsächliche CSS-Lücke\nab) fehlt hier keine CSS-Regel, nur ein API-Hook auf eine bereits vollständige\nKlasse. Siehe Ticket-Bericht für die Abwägung.",
794
+ "description": "Facts (`cds-facts`) — Wrapper um `.ep-facts` aus css/components.css\n(css/components.css:1261–1272): die Definitionsliste für die harten Eckdaten\neines Angebots (Termin, Dauer, Ort, Preis), einspaltig mit Haarlinie zwischen\nden Paaren oder als `.is-grid` zweispaltig für Kästen, die neben Inhalt stehen.\nBewusst ohne eigenen Rahmen und ohne Fläche, sie zieht in einen vorhandenen\nContainer ein (Angebots-Box, Sticky-Sidebar) — Doku-Sektion\n`docs/index.html:9069` (`gt-seminar-fakten`), Beispielseiten `docs/index.html:\n9076–9082`, `13718–13726`, `16293–16300`.\n\n**Element-Selektor, kein Attribut (ADR-0008-Standardfall).** Ausgezählt: keines\nder 3 `.ep-facts`-Vorkommen in `docs/index.html` sitzt in einem `.layout-grid`\nmit `col-*`, keines wird von einem Grid-/Flex-Elternteil in der Höhe gedehnt\n(der Flex-Fall in Zeile 16290 ist `flex-direction:column`, dort füllen Block-\nElemente die Breite ihres Containers unabhängig von einer zusätzlichen\nWrapper-Ebene — anders als bei `.ep-card`/ADR-0008 Fall 1 geht es dort um Höhe,\ndie durch Block-Layout NICHT automatisch nach unten durchgereicht wird). Das\nTag variiert nicht (immer `<dl>`). Analog zu `cds-faq` (`faq.component.ts`, das\nintern `<details>` in einem `<div class=\"ep-faq\">` rendert) trägt hier die\nINNERE `<dl class=\"ep-facts\">` die CSS-Klasse, nicht der `<cds-facts>`-Host: die\nSemantik einer Definitionsliste hängt am `<dl>`-Tag selbst, ein Custom-Element\nkann dieses Tag nicht annehmen. Da `.ep-facts > div + div` ausschließlich\nNachfahren INNERHALB der eigenen Vorlage anspricht, bricht eine zusätzliche\nHost-Ebene darüber nichts — anders als bei `cds-section` (Fall 2) hängt hier\nkeine von außen gesetzte Fläche oder ein von außen adressierter Nachfahre an\ndieser Tiefe.\n\n**`<dl>`/`<div>`/`<dt>`/`<dd>` exakt wie vom CSS erwartet.** `.ep-facts > div +\ndiv` (css/components.css:1262) setzt Haarlinie und Abstand ab dem ZWEITEN Paar;\nohne das umschließende `<div>` pro Paar griffe der Selektor nicht. Jedes\n`items()`-Element wird deshalb zu genau einem `<div><dt>…</dt><dd>…</dd></div>`.\n\n**`area` färbt `<dt>` über `.t-{area}`, nicht über `data-area`.** Anders als bei\n`cds-feature`/`cds-icon-card` kennt `.ep-facts` selbst kein `[data-area]`; der\nCSS-Kommentar zu dieser Klasse (`css/components.css:1259`) benennt\nstattdessen ausdrücklich „Bereichston der Labels über `.t-XX` im Markup\n(dark-safe), nie als Inline-Farbe“ als sanktionierten Weg. Ausgezählt: alle 3\n`.ep-facts`-Vorkommen in `docs/index.html` setzen `.t-wo` auf JEDEM `<dt>`\nderselben Liste (nie gemischt, nie eine andere Bereichsfarbe) — ein optionaler\n`area`-Input, der `.t-{area}` einheitlich auf alle `<dt>` der Instanz anwendet,\nbildet dieses Muster nach, ohne einen Farbwert zu erfinden (`.t-XX` ist eine\nbestehende, bereits kontrast- und dark-mode-geprüfte Utility, `css/components.css\n:638–642`). Ungesetzt (Default) bleibt `<dt>` ohne zusätzliche Klasse, erbt also\ndie umgebende Textfarbe — das deckt Kontexte ohne Bereichsbindung ab.\n\n**Über die ursprüngliche API hinaus:** `area` stand nicht in der ursprünglichen\nAPI-Skizze. Ergänzt, weil ohne ihn KEINES der 3 realen\n`.ep-facts`-Vorkommen originalgetreu nachgebaut werden könnte — anders als bei\n`cds-tier`s `area` (dort deckt die Einschränkung eine tatsächliche CSS-Lücke\nab) fehlt hier keine CSS-Regel, nur ein API-Hook auf eine bereits vollständige\nKlasse.",
795
795
  "docgen": {
796
796
  "$ref": "../services/core/docgen/seitenmuster-seminar-·-training-fakten-liste.json#/components/seitenmuster-seminar-·-training-fakten-liste"
797
797
  },