@conciso/design-system-mcp 2.4.0 → 2.6.0

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 (68) hide show
  1. package/README.md +1 -0
  2. package/package.json +1 -1
  3. package/snapshot/manifests/components.json +207 -27
  4. package/snapshot/manifests/docs.json +0 -128
  5. package/snapshot/services/addon-docs/mdx/grundlagen-farben.json +18 -0
  6. package/snapshot/services/addon-docs/mdx/grundlagen-typografie.json +18 -0
  7. package/snapshot/services/addon-docs/mdx/komponenten-buchungsformular--/303/274bersicht.json +1 -1
  8. package/snapshot/services/addon-docs/mdx/komponenten-buttons-button.json +1 -1
  9. package/snapshot/services/addon-docs/mdx/komponenten-call-to-action-cta-band.json +18 -0
  10. package/snapshot/services/addon-docs/mdx/komponenten-cards-teaser-card.json +18 -0
  11. package/snapshot/services/addon-docs/mdx/komponenten-chips-badges-pills-chip.json +18 -0
  12. package/snapshot/services/addon-docs/mdx/komponenten-code-block-code-block.json +18 -0
  13. package/snapshot/services/addon-docs/mdx/komponenten-dropdowns-custom-select.json +18 -0
  14. package/snapshot/services/addon-docs/mdx/komponenten-feedback-snackbar.json +18 -0
  15. package/snapshot/services/addon-docs/mdx/komponenten-footer-komplett.json +18 -0
  16. package/snapshot/services/addon-docs/mdx/komponenten-hero-hero-bild.json +18 -0
  17. package/snapshot/services/addon-docs/mdx/komponenten-inputs-forms-textfeld.json +18 -0
  18. package/snapshot/services/addon-docs/mdx/komponenten-navigation-topnav.json +18 -0
  19. package/snapshot/services/addon-docs/mdx/komponenten-sektion-sektion.json +18 -0
  20. package/snapshot/services/addon-docs/mdx/komponenten-slider-carousel-carousel.json +18 -0
  21. package/snapshot/services/addon-docs/mdx/komponenten-tabelle-tabelle.json +18 -0
  22. package/snapshot/services/addon-docs/mdx/komponenten-theme-umschalter-cycle-button.json +18 -0
  23. package/snapshot/services/addon-docs/mdx/komponenten-zitate-testimonials-blockquote.json +18 -0
  24. package/snapshot/services/addon-docs/mdx/marke-logo-logo.json +18 -0
  25. package/snapshot/services/core/docgen/komponenten-call-to-action-downloadcta.json +1 -1
  26. package/snapshot/services/core/docgen/komponenten-cards-teaser-feature-liste.json +1 -1
  27. package/snapshot/services/core/docgen/komponenten-cards-teaser-featured-karte.json +1 -1
  28. package/snapshot/services/core/docgen/komponenten-cards-teaser-icon-karte.json +1 -1
  29. package/snapshot/services/core/docgen/komponenten-cards-teaser-klickbare-karte.json +1 -1
  30. package/snapshot/services/core/docgen/komponenten-cards-teaser-statcard.json +1 -1
  31. package/snapshot/services/core/docgen/komponenten-cards-teaser-statstrip.json +1 -1
  32. package/snapshot/services/core/docgen/komponenten-cards-teaser-tier-trenner.json +1 -1
  33. package/snapshot/services/core/docgen/komponenten-chips-badges-pills-bereichs-badge.json +1 -1
  34. package/snapshot/services/core/docgen/komponenten-chips-badges-pills-pill.json +1 -1
  35. package/snapshot/services/core/docgen/komponenten-chips-badges-pills-status-badge.json +1 -1
  36. package/snapshot/services/core/docgen/komponenten-dropdowns-combobox.json +1 -1
  37. package/snapshot/services/core/docgen/komponenten-footer-oberer-teil.json +1 -1
  38. package/snapshot/services/core/docgen/komponenten-footer-unterer-teil.json +1 -1
  39. package/snapshot/services/core/docgen/komponenten-hero-st/303/266rer.json +1 -1
  40. package/snapshot/services/core/docgen/komponenten-inputs-forms-auswahlfeld.json +1 -1
  41. package/snapshot/services/core/docgen/komponenten-inputs-forms-checkbox.json +1 -1
  42. package/snapshot/services/core/docgen/komponenten-inputs-forms-radio.json +1 -1
  43. package/snapshot/services/core/docgen/komponenten-inputs-forms-skala.json +1 -1
  44. package/snapshot/services/core/docgen/komponenten-inputs-forms-slider.json +1 -1
  45. package/snapshot/services/core/docgen/komponenten-inputs-forms-textbereich.json +1 -1
  46. package/snapshot/services/core/docgen/komponenten-slider-carousel-logocarousel.json +1 -1
  47. package/snapshot/services/core/docgen/komponenten-tabelle-vergleichstabelle.json +1 -1
  48. package/snapshot/services/core/docgen/komponenten-theme-umschalter-dropdown.json +1 -1
  49. package/snapshot/services/core/docgen/komponenten-theme-umschalter-segment.json +1 -1
  50. package/snapshot/services/core/docgen/komponenten-zitate-testimonials-teamvoice.json +1 -1
  51. package/snapshot/services/core/docgen/komponenten-zitate-testimonials-testimonial.json +1 -1
  52. package/src/instructions.mjs +9 -1
  53. package/snapshot/services/addon-docs/mdx/komponenten-call-to-action--verwendung.json +0 -18
  54. package/snapshot/services/addon-docs/mdx/komponenten-cards-teaser--verwendung.json +0 -18
  55. package/snapshot/services/addon-docs/mdx/komponenten-chips-badges-pills--verwendung.json +0 -18
  56. package/snapshot/services/addon-docs/mdx/komponenten-code-block--verwendung.json +0 -18
  57. package/snapshot/services/addon-docs/mdx/komponenten-dropdowns--verwendung.json +0 -18
  58. package/snapshot/services/addon-docs/mdx/komponenten-feedback--verwendung.json +0 -18
  59. package/snapshot/services/addon-docs/mdx/komponenten-footer--verwendung.json +0 -18
  60. package/snapshot/services/addon-docs/mdx/komponenten-hero--/303/274bersicht.json +0 -18
  61. package/snapshot/services/addon-docs/mdx/komponenten-inputs-forms--verwendung.json +0 -18
  62. package/snapshot/services/addon-docs/mdx/komponenten-navigation--verwendung.json +0 -18
  63. package/snapshot/services/addon-docs/mdx/komponenten-sektion--/303/274bersicht.json +0 -18
  64. package/snapshot/services/addon-docs/mdx/komponenten-slider-carousel--verwendung.json +0 -18
  65. package/snapshot/services/addon-docs/mdx/komponenten-tabelle--/303/274bersicht.json +0 -18
  66. package/snapshot/services/addon-docs/mdx/komponenten-theme-umschalter--verwendung.json +0 -18
  67. package/snapshot/services/addon-docs/mdx/komponenten-zitate-testimonials--verwendung.json +0 -18
  68. package/snapshot/services/addon-docs/mdx/marke-logo--verwendung.json +0 -18
@@ -0,0 +1,18 @@
1
+ {
2
+ "components": {
3
+ "komponenten-footer-komplett": {
4
+ "id": "komponenten-footer-komplett",
5
+ "name": "komponenten-footer-komplett",
6
+ "docs": {
7
+ "komponenten-footer-komplett--verwendung": {
8
+ "id": "komponenten-footer-komplett--verwendung",
9
+ "name": "Verwendung",
10
+ "path": "./src/docs/komponenten/footer-verwendung.mdx",
11
+ "title": "Komponenten/Footer/Komplett",
12
+ "content": "import { Meta, Unstyled } from '@storybook/addon-docs/blocks';\nimport * as FooterStories from '../../lib/footer/footer.stories';\n\n<Meta of={FooterStories} name=\"Verwendung\" />\n\n# Verwendung\n\nFooter ist ein Zwei-Band-Layout: heller Main-Bereich mit Spalten-Grid (Brand & Kontakt ·\nWichtige Inhalte · Contentletter abonnieren) und dunkler Bottom-Streifen für Copyright,\nPflichtangaben und Social-Icons. Drei Stories bilden das ab: `Footer/Komplett` (die volle Demo),\n`Footer/Oberer Teil` (das Main-Band) und `Footer/Unterer Teil` (der Bottom-Streifen).\n`cds-footer-main` ist dabei ein generisches Spalten-Layout: Jedes Top-Level-Kind wird\neine eigene Grid-Spalte, die Spaltenzahl ist nicht fest auf drei begrenzt.\n\n## Anatomie\n\nDas folgende Schema zeigt die fünf nummerierten Zonen des Footers, von der\nBrand-Spalte links bis zu den Social-Icons rechts im dunklen Bottom-Streifen. Es\nergänzt die Spalten-Struktur unten um die räumliche Anordnung.\n\n<Unstyled>\n <div\n style={{\n border: 'var(--bd-strong)',\n borderRadius: 'var(--r-lg)',\n overflow: 'hidden',\n font: '400 12px/16px var(--font)',\n }}\n >\n <div\n style={{\n display: 'grid',\n gridTemplateColumns: '1.2fr 1fr 1.3fr',\n gap: 0,\n borderBottom: 'var(--bd)',\n background: 'var(--n-50)',\n }}\n >\n <div style={{ padding: 'var(--s4)', borderRight: 'var(--bd)' }}>\n <div className=\"t-co\" style={{ fontWeight: 700, marginBottom: '4px' }}>\n ① Brand & Kontakt\n </div>\n <div style={{ color: 'var(--tx-muted)' }}>Wortmarke + Adresse + Karten + Telefon/E-Mail</div>\n </div>\n <div style={{ padding: 'var(--s4)', borderRight: 'var(--bd)' }}>\n <div style={{ fontWeight: 600, color: 'var(--tx-primary)', marginBottom: '4px' }}>\n ② Wichtige Inhalte\n </div>\n <div style={{ color: 'var(--tx-muted)' }}>\n Sechs Top-Level-Links\n <br />\n (Bereiche · Beiträge · Jobs · Kontakt)\n </div>\n </div>\n <div style={{ padding: 'var(--s4)' }}>\n <div className=\"t-co\" style={{ fontWeight: 700, marginBottom: '4px' }}>\n ③ Contentletter abonnieren\n </div>\n <div style={{ color: 'var(--tx-muted)' }}>Lead + Name-Feld + E-Mail-Feld + Consent + Pill-Button</div>\n </div>\n </div>\n <div\n style={{\n padding: 'var(--s3) var(--s4)',\n background: 'var(--n-700)',\n color: 'var(--n-300)',\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--s4)',\n }}\n >\n <span>④ © Jahr Firma · Standort · Datenschutz · Impressum</span>\n <span style={{ marginLeft: 'auto' }}>⑤ LinkedIn · YouTube</span>\n </div>\n </div>\n</Unstyled>\n\n## Spalten-Struktur\n\n| Spalte | Inhalt | Hinweis |\n|---|---|---|\n| 1, Brand & Kontakt | Wort-Marke „Conciso GmbH“ (`.footer-brand`) plus Postadresse, Karten-Links (Google Maps, OpenStreetMap, Apple Karten), Telefon, Fax, E-Mail, als `<address class=\"footer-address\">` semantisch markiert | Breitere Spalte (1.2fr). Kein Logo-Bild, weil die sticky Topnav das Logo bereits trägt |\n| 2, Wichtige Inhalte | Eine flache Liste mit sechs Top-Level-Zielen: Angewandte KI · Effektive Software · Wirksame Organisationen · Beiträge · Jobs · Kontakt | Bewusst konsolidiert statt nach Themen aufgeteilt, der Footer ist Wiederfindungs-Anker, keine zweite Hauptnavigation |\n| 3, Contentletter abonnieren | Gestapelte Form: Lead → Name-Feld → E-Mail-Feld → Datenschutz-Consent → Anmelden-Button (Pill, natürliche Breite) | Consent steht vor dem Submit, damit Tab- und Klickpfad keine Anmeldung ohne gelesene Datenschutzinfo erlauben (DSGVO und Trust) |\n| Bottom-Zeile | Dunkler Streifen: Copyright plus Pflichtangaben (Datenschutz, Impressum) linksbündig, Social-Icons (LinkedIn, YouTube) rechtsbündig via `margin-left:auto` | Pflichtangaben links, der sticky Back-to-Top-Button rechts unten würde rechts platzierte Links sonst überlagern |\n\n## Typografie: ruhige Utility-Zone\n\n`.footer-brand` und `.footer-htitle` nutzen `--ty-title-sm` (Sans 500, 18/26 px),\nbewusst ohne Uppercase und ohne Letter-Spacing. Der Footer ist eine\nWiederfindungs- und Pflichtangaben-Zone, keine editoriale Bühne: Familie (Sans statt\nSerif), Größe (18 px statt 28 px Headline-md) und Gewicht (500 statt 400 Serif) rücken\ndie Headlines gegenüber Section-H2 bewusst in den Hintergrund. Caps und Letter-Spacing\nwürden ein zweites Konversions-Signal neben dem Page-End-CTA-Band darüber erzeugen,\nReibung statt Ruhe.\n\n| Klasse | Token | Rolle |\n|---|---|---|\n| `.footer-brand` | `--ty-title-sm` | Wortmarke „Conciso GmbH“ in Spalte 1 |\n| `.footer-htitle` | `--ty-title-sm` | Spalten-Titel („Wichtige Inhalte“, „Contentletter abonnieren“) |\n| `.footer-subtitle` | Label-Größe (12 px) | Optionale Zwischen-Überschrift bei Sub-Area-Aufgliederung |\n\n## Contentletter-Form\n\nEine bewusst leisere Variante der Form-Komponente: Felder sind kompakter (14 px Body\nstatt 16 px) und die Border ist 1 px statt 2 px, passend zur Utility-Zone. Labels sind\nidentisch mit `.field label` (Uppercase, `--ty-label-sm`, letter-spacing .06em,\n`--tx-secondary`), damit die Form-Sprache zonenübergreifend gleich liest.\n\n| Klasse | Rolle | Hinweis |\n|---|---|---|\n| `.footer-newsletter-form` | Form-Container | Submit-Button via `align-self:flex-start` auf natürliche Pill-Breite |\n| `.footer-field` | Feld-Wrapper (`<label>`) | Implizite Label-Input-Verknüpfung, keine `for`/`id`-Paare nötig |\n| `.footer-field-label` | Sichtbarer Label-Text | `--ty-label-sm`, Uppercase, letter-spacing .06em |\n| `.footer-field.has-error` | Inline-Validierungs-State | Border kippt auf `--c-error`, Fehlertext via `.error-msg` |\n| `.footer-newsletter-consent` | DSGVO-Checkbox-Zeile | Sitzt zwingend vor dem Submit |\n\nDer Rechtstext in der Einwilligung ist ein echtes `<a class=\"body-link\">`, nie ein\n`<span>` mit `cursor:pointer` (CONTRIBUTING § 8): Ein Span ist per Tab nicht erreichbar\nund für Screenreader kein Link, obwohl genau dieser Text die Grundlage der Einwilligung\nist. Im `<label for>` ist der Anker unkritisch, die Label-Aktivierung läuft bei\ninteraktiven Nachfahren nicht, ein Klick auf den Link setzt kein Häkchen.\n\n## Sub-Hierarchie\n\nAktuell nutzt keine Footer-Spalte eine Sub-Hierarchie. Soll eine Spalte künftig in\nSub-Areas mit eigenen Listen aufgegliedert werden, steht dafür `.footer-subtitle`\nbereit: eine Zwischen-Überschrift auf Label-Größe, eine Stufe unter `.footer-htitle`,\ndie eine zweite konkurrierende Headline-Ebene innerhalb einer Spalte vermeidet.\n\n## Barrierefreiheit\n\n| Aspekt | Regel |\n|---|---|\n| Landmark | `<footer>` mit `aria-label=\"Seitenfuß\"`; jede eingebettete `<nav>` braucht ein eigenes `aria-label` (zum Beispiel „Wichtige Inhalte“, „Rechtliche Hinweise“, „Soziale Netzwerke“), damit Screenreader Haupt- und Footer-Navigation unterscheiden |\n| Adresse | Postanschrift und Kontaktdaten als `<address>` semantisch markiert |\n| Karten-Links | Externe Ziele (`target=\"_blank\"`) tragen `rel=\"noopener\"` und ein `aria-label`, das Ziel und „neues Tab“ nennt |\n| Social-Icons | Bild-Links ohne sichtbaren Text brauchen ein `aria-label` auf dem `<a>` (das Icon-Bild selbst mit leerem `alt=\"\"`) |\n| Consent-Link | Der Rechtstext in der Contentletter-Einwilligung ist ein echtes `<a class=\"body-link\">`, nie ein `<span>` mit `cursor:pointer` (CONTRIBUTING § 8) |\n| Reihenfolge im Formular | Die Consent-Checkbox steht vor dem Submit-Button, Tab- und Klickpfad können ohne die Einwilligung nicht abschließen |\n\n## Dos & Don'ts\n\n**Tun**\n\n- Wortmarke „Conciso GmbH“ als Brand-Anker im Main-Band verwenden, die sticky Topnav trägt das Logo schon, ein Logo-Bild hier wäre Doppelung\n- Navigation als konsolidierte Spalte „Wichtige Inhalte“ mit maximal 6 bis 8 Top-Level-Zielen führen, der Footer ist Wiederfindungs-Anker, keine zweite Hauptnavigation\n- Jede `<nav>` mit eindeutigem `aria-label` versehen (Wichtige Inhalte · Rechtliche Hinweise · Soziale Netzwerke)\n- Datenschutz und Impressum linksbündig in der Bottom-Bar setzen, verhindert Kollision mit dem sticky Back-to-Top-Button rechts unten\n- Consent vor Submit in der Contentletter-Form platzieren, Tab- und Klickpfad führt durch die Einwilligung, bevor abgesendet wird (DSGVO und Trust)\n- Spalten-Headlines (`.footer-brand`, `.footer-htitle`) in Title-sm halten, Mixed Case, Sans 500, 18 px, der Footer ist Wiederfindungs-Zone, keine Headline-Bühne\n\n**Nicht tun**\n\n- Logo-Bild im Brand-Bereich zeigen, redundant zur sticky Topnav, die die Marke bereits trägt\n- Spalten-Headlines auf Headline-Serifen-Token oder Uppercase plus Letter-Spacing setzen, sie würden mit dem Erstgespräch-CTA-Band darüber um Aufmerksamkeit konkurrieren\n- Footer als primäre Navigation verwenden, er ist eine Ergänzung, kein Ersatz für die Topnav\n- Mehr als etwa 8 Items in der Wichtige-Inhalte-Liste führen, der Footer würde zur Mini-Sitemap, tiefere Inhalte gehören auf den jeweiligen Bereichs-Hub\n- Datenschutz und Impressum rechts in der Bottom-Bar oder in der Wichtige-Inhalte-Spalte platzieren, sie gehören in den dunklen Bottom-Streifen linksbündig\n- Footer ohne Copyright-Jahr und Firmennamen ausliefern, beide sind rechtlich relevant und müssen aktuell sein\n\n## Verwandte Seiten\n\n- Komponenten/Footer/Komplett\n- Komponenten/Footer/Oberer Teil\n- Komponenten/Footer/Unterer Teil\n",
13
+ "summary": "# Verwendung Footer ist ein Zwei-Band-Layout: heller Main-Bereich mit Spalten-Grid (Brand ..."
14
+ }
15
+ }
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "components": {
3
+ "komponenten-hero-hero-bild": {
4
+ "id": "komponenten-hero-hero-bild",
5
+ "name": "komponenten-hero-hero-bild",
6
+ "docs": {
7
+ "komponenten-hero-hero-bild--verwendung": {
8
+ "id": "komponenten-hero-hero-bild--verwendung",
9
+ "name": "Verwendung",
10
+ "path": "./src/docs/komponenten/hero.mdx",
11
+ "title": "Komponenten/Hero/Hero-Bild",
12
+ "content": "import { Meta, Unstyled } from '@storybook/addon-docs/blocks';\nimport * as HeroImageStories from '../../lib/hero-image/hero-image.stories';\n\n<Meta of={HeroImageStories} name=\"Verwendung\" />\n\n# Hero\n\nDrei Hero-Varianten für den Seitenkopf: Hero-Image mit Caption-Overlay (21:9,\nStandard auf allen Customer-Pages), Slider-Hero mit Crossfade-Rotation (optional)\nsowie eine reduzierte Sub-Sektion direkt unter dem Hero für CTAs (auf jeder\nCustomer-Page Pflicht). Dazu der Störer, der ausschließlich auf der Startseite über\ndem Hero-Bild liegt. Hero-Image und Störer haben eigene Angular-Bauteile:\n`Komponenten/Hero/Hero-Bild` (`cds-hero-image`) und `Komponenten/Hero/Störer`\n(`cds-stoerer` in `cds-stoerer-set`). Slider-Hero und Sub-Sektion haben keins, ihre\nKlassen kommen direkt aus der CSS-Schicht. Der Slider-Hero teilt seine Track-Mechanik\nmit `Komponenten/Slider & Carousel/Carousel`.\n\nQuelle: `docs/index.html#sec-hero` (Zeilen 2550 bis 2995).\n\n## Hero-Image mit Caption-Overlay\n\nVollbreit, 21:9-Format, Caption als Gradient-Overlay über dem Bild, ohne\nTrack-Mechanik, ohne Buttons und Dots, ohne JavaScript. Klasse `.hero-image` als\n`<figure>`-Container; semantisch ist Bild und Caption ein figure/figcaption-Pattern.\nVerwenden, wenn die Bildaussage stark genug ist, dass keine Rotation nötig wäre, und\nwenn die Marken-Headline besser am Bild verankert sitzt als in einer separaten\nHero-Body-Spalte.\n\nOptionale Eyebrow-Zeile `.hero-image-caption-eyebrow` direkt vor dem Titel,\nVersalien mit Letter-Spacing, identisch zur Eyebrow-Logik im Standard-Hero. Auf\nBereichs-Pages enthält sie typischerweise die drei Werte des Bereichs, auf der\nLanding den Zeit-Ort-Anker.\n\n**Höhe und Skalierung auf breiten Bildschirmen.** Der Hero zielt auf rund 75 % der\nViewport-Höhe. Das 21:9-Format würde auf 4K- oder Ultrawide-Monitoren rechnerisch\naber noch höher werden (auf 3.840 px Breite wären das 1.646 px Höhe). Damit\nGesichter nicht ins Gigantische kippen, ist `.hero-image-media` über\n`max-height:clamp(480px,75vh,900px)` gedeckelt: mindestens 480 px auf flachen\nFenstern, bevorzugt 75 vh, gedeckelt bei 900 px. Dieselbe Logik liegt auf dem\nSlider-Hero. Die Caption bleibt durch `padding-inline:max(var(--s8),calc(50% - 640px))`\nin einer 1280-px-Spalte zentriert.\n\n**Object-Position pro Bild.** Sobald der Cap greift, beschneidet\n`object-fit:cover` das Bild zusätzlich vertikal. Für Bilder mit zentralen\nGesichtern oder asymmetrischer Komposition den passenden `object-position`-Wert\ndirekt am `<img>`-Element setzen (z. B. `style=\"object-position:center 20%\"`).\nDefault ist `center center`.\n\nDas folgende Schema zeigt die Bildfläche im 21:9-Format mit dem Caption-Overlay\ndarüber: Eyebrow, Headline und Sub-Text liegen als Gradient von unten nach oben\nüber dem Bild.\n\n<Unstyled>\n <div style={{ border: 'var(--bd-strong)', borderRadius: 'var(--r-lg)', overflow: 'hidden', marginBottom: 'var(--s8)' }}>\n {/* Hero-Image-Schema: Bildfläche mit Caption-Overlay */}\n <div\n style={{\n position: 'relative',\n minHeight: '240px',\n background: 'linear-gradient(135deg,var(--n-300) 0%,var(--n-200) 55%,var(--n-300) 100%)',\n }}\n >\n <div\n style={{\n position: 'absolute',\n top: 'var(--s4)',\n left: 'var(--s5)',\n font: '500 12px/14px var(--font)',\n letterSpacing: '.1em',\n textTransform: 'uppercase',\n color: 'var(--n-500)',\n }}\n >\n Bildfläche · 21:9 · object-fit:cover\n </div>\n {/* Caption-Overlay (.hero-image-caption) */}\n <div\n style={{\n position: 'absolute',\n left: 0,\n right: 0,\n bottom: 0,\n padding: 'var(--s7) var(--s6) var(--s5)',\n background: 'linear-gradient(to top,rgba(0,0,0,.85) 0%,rgba(0,0,0,.65) 50%,transparent 100%)',\n }}\n >\n <div\n style={{\n font: '500 12px/16px var(--font)',\n letterSpacing: '.1em',\n textTransform: 'uppercase',\n color: 'rgba(255,255,255,.85)',\n marginBottom: 'var(--s2)',\n }}\n >\n ④ Eyebrow, Bereichskontext oder Zeit-Ort\n </div>\n <div style={{ font: '400 28px/34px var(--font-display)', color: '#fff', marginBottom: 'var(--s2)' }}>\n ⑤ Headline (Caption-Title)\n </div>\n <div style={{ font: '400 14px/20px var(--font)', color: 'rgba(255,255,255,.92)', maxWidth: '520px' }}>\n ⑥ Sub-Text: konkret und prägnant, max. 2 Sätze.\n </div>\n </div>\n </div>\n </div>\n</Unstyled>\n\n```html\n<!-- Hero-Image: 21:9, Eyebrow + Titel + Text als Gradient-Overlay -->\n<figure class=\"hero-image\">\n <div class=\"hero-image-media\">\n <img src=\"hero.jpg\" alt=\"Kurze Bildbeschreibung\" loading=\"eager\">\n </div>\n <figcaption class=\"hero-image-caption\">\n <p class=\"hero-image-caption-eyebrow\">Seit 2016 · Dortmund</p>\n <h1 class=\"hero-image-caption-title\">Klare Köpfe. Ruhige Energie.</h1>\n <p class=\"hero-image-caption-text\">KI, Software und Organisationsentwicklung aus Dortmund.</p>\n </figcaption>\n</figure>\n```\n\n### Content-breite Akzentbilder (`.ep-media-band`)\n\nZwischen den Sektionen lockern gelegentlich Akzentbilder (Menschen,\nWorkshop-Atmosphäre) den Lesefluss auf. Anders als das vollbreite Hero-Image laufen\nsie nicht randlos, sondern auf Content-Breite: `.ep-media-band` als `<figure>`\nhängt an derselben Zentrier-Regel wie `.ep-section`\n(`padding-inline:max(var(--s8),calc(50% - 640px))`) und rastet damit pixelgenau auf\ndie 1280-px-Spalte ein, bündig mit Text und Karten. Ein randloses Zwischenbild\nwürde auf breiten Schirmen zu einem sehr breiten, flachen Band mit unkontrolliertem\nBeschnitt: Die Proportionen kippen dann je nach Fensterbreite. Auf Content-Breite\ngedeckelt bleiben sie stabil, den randlosen Auftritt behält allein der Hero. Die\nEcken sind mit `--r-lg` gerundet wie Karten, der Radius liegt auf dem `<img>`, nicht\nauf dem `<figure>` (das ist durch das Gutter-Padding breiter als das sichtbare\nBild). Das Band trägt `margin:var(--s12) 0`, damit es als eigenständiges Element\nauf dem Seitengrund steht und nicht an einer angrenzenden vollbreiten Farbfläche\nklebt.\n\n## Slider-Hero\n\n**Status.** Aktuell auf keiner Beispielseite produktiv eingesetzt, der statische\nHero-Image ist der Standard. Der Slider-Hero ist eine optionale Alternative, wenn\nmehrere gleichwertige Aussagen rotieren sollen (z. B. wechselnde Kundenprojekte auf\neiner Referenz-Übersicht). Aufmerksamkeits-Konkurrenz mit der Marken-Headline\neinplanen.\n\nVollbreit, randlos, 21:9-Format. Caption liegt als Gradient-Overlay über dem Bild.\nModifier-Klasse `.img-slider-hero` zum Basis-Element hinzufügen.\n\n**Übergang.** Crossfade über 600 ms. Slides liegen absolut übereinander; die aktive\nSlide trägt die Klasse `.active` (initial im HTML auf der ersten Slide setzen,\ndamit beim Page-Load nichts unsichtbar ist). Unter `prefers-reduced-motion:reduce`\nentfällt die Transition automatisch.\n\n**Skalierung auf breiten Bildschirmen.** `.img-slider-hero .img-slider-track` ist\nüber `max-height:clamp(420px,64vh,680px)` gedeckelt, damit der Hero auf 4K- und\nUltrawide-Monitoren nicht die gesamte Viewport-Höhe einnimmt. Gleiche Logik wie\nbeim statischen Hero-Image.\n\n**21:9 gegenüber 16:9.** Hero und Slider-Hero laufen auf `.hero-image-media` und\n`.img-slider-hero .img-slider-track` mit `aspect-ratio:21/9`. Der Basis-Slider\naußerhalb des Hero-Kontexts (`.img-slide-media`, genutzt in\n`Komponenten/Slider & Carousel/Carousel`) und `.card-media` in\n`Komponenten/Cards & Teaser/Card` laufen dagegen mit `aspect-ratio:16/9`. Ein Hero\nist breiter und flacher als eine Karte, weil er die volle Seitenbreite trägt statt\neiner begrenzten Spalte.\n\n```html\n<!-- Modifier .img-slider-hero für Vollbild-Darstellung mit Crossfade -->\n<div class=\"img-slider img-slider-hero\"\n role=\"region\" aria-roledescription=\"Bildschirmpräsentation\" aria-label=\"Hero-Bildstrecke\">\n <div class=\"img-slider-track\">\n <!-- erste Slide trägt .active, damit sie ohne JS sofort sichtbar ist -->\n <div class=\"img-slide active\" role=\"group\" aria-roledescription=\"Folie\" aria-label=\"Folie 1 von 4\">\n <div class=\"img-slide-media\">\n <img src=\"hero.jpg\" alt=\"Kurze Bildbeschreibung\">\n </div>\n <div class=\"img-slide-caption\">\n <p class=\"img-slide-caption-title\">Titel</p>\n <p class=\"img-slide-caption-text\">Beschreibung</p>\n </div>\n </div>\n <!-- weitere .img-slide … -->\n </div>\n <button class=\"img-slider-btn img-slider-prev\" aria-label=\"Vorherige Folie\">…</button>\n <button class=\"img-slider-btn img-slider-next\" aria-label=\"Nächste Folie\">…</button>\n <div class=\"img-slider-dots\" role=\"tablist\" aria-label=\"Folien-Navigation\">\n <button class=\"img-dot\" role=\"tab\" aria-selected=\"true\" aria-label=\"Folie 1 von 4\"></button>\n <button class=\"img-dot\" role=\"tab\" aria-selected=\"false\" aria-label=\"Folie 2 von 4\"></button>\n </div>\n</div>\n```\n\n## Reduzierte Sub-Sektion mit CTAs\n\nDirekt unterhalb des Heros sitzt eine schmale, getönte Sektion (`.ep-section`,\nzentriert, Padding `var(--s10)`) mit Eyebrow, kurzer Lead-Zeile und zwei CTAs. Sie\nübernimmt die Funktion „Was tun?“, ohne mit der Hero-Headline zu konkurrieren: Der\nHero macht das Versprechen, die Sektion darunter macht das Angebot. Eine\nAngebots-Sektion direkt unter dem Hero-Image ist auf jeder Customer-Page Pflicht,\nihre Form variiert (schmaler Sub-Strip oder, auf Bereichs-Pages mit\nManifest-Charakter, eine reichere Manifest-Intro).\n\n**Bereichs-Tinting.** Hintergrund und untere Trennlinie folgen dem Page-Bereich,\nnicht pauschal Corporate.\n\n| Page | Eyebrow-Text | Eyebrow-Farbe |\n|---|---|---|\n| Landing | „Verbunden gedacht“ (Brücke-Variante B) | `--co-700` |\n| Angewandte KI | Leistungs-Achsen: „AI.Engineering · AI.Automation · AI.Box“ | `--ki-800` |\n| Effektive Software | Manifest-Intro (Variante C): „Was effektive Software ausmacht“ | `--es-700` |\n| Wirksame Organisationen | Leistungs-Achsen: „Change · Leadership · Struktur“ | `--wo-700` |\n| Unternehmen | Profil-Hub-Achsen: „Team · Karriere · Referenzen“ | `--co-700` |\n\n**CTAs pro Page-Typ.** Filled (primär, Selbst-Erschließung) plus Outlined\n(sekundär, Direkt-Kontakt oder Belege), beide in der Bereichsfarbe.\n\n| Page | Primär (Filled) | Sekundär (Outlined) |\n|---|---|---|\n| Landing | Keine CTAs (Brücke-Variante B) | — |\n| Angewandte KI | Use Case analysieren | Fallstudien ansehen |\n| Effektive Software | Unsere Leistungen ansehen | Gespräch anfragen |\n| Wirksame Organisationen | Transformation starten | Ansatz kennenlernen |\n| Unternehmen | Team kennenlernen | Karriere |\n\nWichtig: Wenn Hero und Sub-Strip zusammen verwendet werden, darf der Sub-Strip\nkeine eigene H1 und keine wiederholte Was/Wo-Aussage tragen, sonst entstehen\nDoppel-Informationen direkt untereinander.\n\n| Variante | Inhalt | Wann einsetzen |\n|---|---|---|\n| A · mit CTAs | Eyebrow, Lead, zwei Buttons (Filled primär, Outlined sekundär) | Standard, wenn die Sub-Sektion zwei direkte Aktionen anbieten soll |\n| B · Brücke ohne CTAs | Eyebrow, Lead, Drei-Spalter mit Bereichs-Eyebrows | Wenn der Sticky-Topnav-Kontakt-Button bereits die primäre Konversion trägt |\n| C · Manifest-Intro | Eyebrow, Lead (erster Satz in `--ty-serif-md`), Fließtext, zwei CTAs, zweispaltiges Grid | Auf Bereichs-Pages, deren Einstieg eine Haltung oder Definition transportiert |\n\nDas folgende Schema zeigt Variante A: Markenrad-Eyebrow, Lead-Zeile und die zwei\nCTAs (Filled primär, Outlined sekundär) auf dem getönten Sub-Strip-Hintergrund.\n\n<Unstyled>\n <div style={{ border: 'var(--bd-strong)', borderRadius: 'var(--r-lg)', overflow: 'hidden', marginBottom: 'var(--s8)' }}>\n {/* Sub-Strip-Schema: Markenrad-Eyebrow + Lead + 2 CTAs */}\n <div style={{ background: 'var(--co-50)', textAlign: 'center', padding: 'var(--s7) var(--s6)' }}>\n <div\n className=\"t-co\"\n style={{\n font: '500 12px/16px var(--font)',\n letterSpacing: '.1em',\n textTransform: 'uppercase',\n marginBottom: 'var(--s2)',\n }}\n >\n ⑦ Markenrad-Eyebrow „Ruhig · Klar · Energiegeladen“\n </div>\n <div\n style={{\n font: '400 14px/20px var(--font)',\n color: 'var(--tx-secondary)',\n marginBottom: 'var(--s4)',\n maxWidth: '520px',\n marginLeft: 'auto',\n marginRight: 'auto',\n }}\n >\n ⑧ Lead-Zeile: Brücke zu den CTAs, nicht das Hero-Versprechen echoen.\n </div>\n <div style={{ display: 'flex', gap: 'var(--s3)', justifyContent: 'center', flexWrap: 'wrap' }}>\n <div\n style={{\n background: 'var(--co-700)',\n borderRadius: 'var(--r-full)',\n padding: '8px 20px',\n font: 'var(--ty-name)',\n color: '#fff',\n }}\n >\n ⑨ Primäraktion (Filled)\n </div>\n <div\n className=\"t-co\"\n style={{\n border: '2px solid var(--co-700)',\n borderRadius: 'var(--r-full)',\n padding: '6px 18px',\n font: 'var(--ty-name)',\n }}\n >\n ⑩ Sekundäraktion (Outlined)\n </div>\n </div>\n </div>\n </div>\n</Unstyled>\n\n```html\n<!-- Sub-Strip: Bereichs-Tint, Eyebrow, Lead, 2 CTAs (Werte hier: Landing/Corporate) -->\n<div class=\"ep-section\" style=\"background:var(--co-50);text-align:center;padding-block:var(--s10);border-bottom:1px solid var(--co-100)\">\n <p class=\"ep-hero-eyebrow\" style=\"color:var(--co-700)\">Ruhig · Klar · Energiegeladen</p>\n <p>Schau Dir an, wie wir arbeiten, oder buch direkt ein Erstgespräch.</p>\n <div class=\"ep-hero-ctas\" style=\"justify-content:center\">\n <button class=\"btn btn-filled\" style=\"--c500:var(--co-700)\">Unsere Leistungen</button>\n <button class=\"btn btn-outlined\" style=\"--c500:var(--co-700)\">Gespräch anfragen</button>\n </div>\n</div>\n```\n\n## Störer über dem Hero (Startseite)\n\nKompakte Verweiskacheln, die als Set oben rechts über dem Hero-Bild liegen und je\nauf einen aktuellen Inhalt zeigen: die nächste Veranstaltung, einen neuen\nWissensbeitrag, eine Pressemitteilung oder eine allgemeine Info (neue\nSeminar-Landingpage, neues Produkt). Klassen `.stoerer-hero` (Wrapper um Hero und\nSet), `.stoerer-set`, `.stoerer-list`, `.stoerer`.\n\n**Nur auf der Startseite.** Auf Bereichs- und Detailseiten trägt der Hero genau\neine Aussage; ein zweiter Aufmerksamkeitspunkt daneben würde sie schwächen. Die\nWeiterleitung übernimmt dort die reduzierte Sub-Sektion unter dem Hero.\n\n**Das Ziel ist immer die eigene Seite des Inhalts, nie ein Anker auf der\nStartseite.** Ein Klick auf die Veranstaltungs-Kachel führt auf die\nVeranstaltungs-Detailseite (`Seitenmuster/Veranstaltung`), die Wissens-Kachel auf\nden Beitrag (`Seitenmuster/Wissensbeitrag`). Der Störer ist eine Abkürzung zum\nInhalt, keine Sprungmarke innerhalb der Startseite. Die Sektion bleibt der\nkanonische Ort, sie führt Bild, Anreißer und Kontext, der Störer führt nur Typ,\nTitel und Meta. Daraus folgt eine Pflichtregel: Das Thema-Label spiegelt das\nVokabular der zugehörigen Sektion, es führt kein zweites Wort für dieselbe Domäne\nein.\n\n**Ein Set, keine Einzelkacheln.** Alle Kacheln eines Sets haben dieselbe Breite\n(`--stoerer-w`, Default 22,5rem/360 px), denselben Aufbau, dieselbe\nTypo-Hierarchie und dieselben Abstände. Die Höhe hält `.stoerer-title` gleich:\n`min-height:2lh` reserviert zwei Zeilen, das innere Element kappt längere Titel\nper `line-clamp`. Default sind zwei Kacheln, drei sind das Maximum.\n\n**Eine Farbgebung für alle vier Typen.** Der Inhaltstyp steht im Thema-Label und im\nIcon, nie in der Farbe. Vier Bereichsfarben nebeneinander würden gegeneinander und\ngegen die Hero-Headline arbeiten. Der Akzent ist deshalb durchgehend Corporate\n(`--co-ink`), aus demselben Grund wie bei der Topnav: der Störer ist Chrome über\ndem Bild, kein Bereichsinhalt. Ein `data-area` gibt es hier bewusst nicht.\n\n**Deckende Fläche, kein Glas.** Über einem Foto ist der Grund unbekannt, eine\nhalbtransparente Kachel trägt je nach Bildstelle 2:1 oder 12:1, und keine Regel\nkann das absichern. Auf `--bg-surface` steht der Text auf einer bekannten Fläche\nund ist messbar: Light 5,52 bis 10,92:1, Dark 8,32 bis 10,48:1. Dazu trägt die\nKachel schon im Light einen `--bd-strong`-Rahmen, abweichend von den übrigen\nKarten: auf einer ausgebrannt hellen Bildstelle leistet der schwarze `--e1`-Schatten\nkeine Kante mehr.\n\n**Das Typ-Glyph sitzt im Thema-Label, nicht rechts hinter einem Trenner.** Das\nGlyph ist ein reiner Typ-Marker, also eine Kategorie, und gehört als\nLeading-Element vor das Label. Rechts stand es im Slot für Aktionen und Status,\nauf einer vollständig klickbaren Kachel also doppeldeutig (kann als Button\nlesen). Ohne Icon-Spalte wächst die Textspalte von 269 auf 326 px (plus 21 %), und\ndie realistischen Titel der Startseite klammern nicht mehr.\n\n**Platzierung.** Oben rechts, weil die Caption unten links liegt und die\nSub-Sektion direkt darunter die freie Zone bildet. Das Hero-Bild muss dafür\nausgewählt sein: die Kacheln verdecken das obere rechte Viertel deckend, dort darf\nalso kein tragendes Bildmotiv liegen (Gesichter, Produkt, Logo).\n\n**Container-Query statt Fensterbreite.** Bei 21:9 ist die Hero-Höhe die Breite ×\n9/21. Eine Kachel ist 126 px hoch, drei plus Abstände und der Rand oben brauchen\n426 px Höhe und damit 994 px Hero-Breite. Ob die da ist, sagt der Hero, nicht das\nFenster: Eine Media Query auf 1025 px ließ ein Dreier-Set in einem 1200-px-Fenster\nmit 898-px-Hero 41 px in die Sub-Sektion ragen. Die Entscheidung hängt deshalb an\neiner Container-Query auf `.stoerer-hero`, Schwelle 64,0625rem (1025 px), dem\nDesktop-Tier der Responsive-Strategie:\n\n```css\n.stoerer-hero{position:relative;container-type:inline-size}\n\n@container (min-width:64.0625rem){\n .stoerer-set{position:absolute;top:var(--s6);right:var(--s8)}\n .stoerer-list{display:flex;flex-direction:column;width:var(--stoerer-w)}\n}\n```\n\nAußerhalb der `@container`-Regel (schmaler Hero oder ein Browser ohne\nContainer-Query-Support) bleibt `.stoerer-set` ein normaler Block unter dem Hero,\nein- oder zweispaltig je nach Platz: Das ist der Default-Zweig, und er ist zugleich\nder funktionierende Fall, nie ein Overlay, das halb in die Sub-Sektion ragt.\n\nDas folgende Specimen zeigt alle vier Inhaltstypen nebeneinander: gleiche\nFläche, gleiche Typo-Hierarchie, unterschieden allein durch Thema-Label und\nIcon. Im Einsatz stehen höchstens drei Kacheln zusammen, deshalb trägt dieses\nSpecimen eine breitere, abweichende Grid-Spalte; die `href`-Werte sind\nPlatzhalter, im Einsatz zeigt jede Kachel auf die Detailseite ihres Inhalts\n(siehe Tabelle unten).\n\n<Unstyled>\n {/* Störer-Specimen: alle vier Inhaltstypen nebeneinander */}\n <ul\n className=\"stoerer-list\"\n style={{\n display: 'grid',\n gridTemplateColumns: 'repeat(auto-fit,minmax(320px,1fr))',\n width: 'auto',\n marginBottom: 'var(--s8)',\n }}\n >\n <li>\n <a className=\"stoerer\" href=\"#gt-hero-stoerer\">\n <span className=\"stoerer-topic\">\n <svg className=\"stoerer-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 0 1 2.25-2.25h13.5A2.25 2.25 0 0 1 21 7.5v11.25m-18 0A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75m-18 0v-7.5A2.25 2.25 0 0 1 5.25 9h13.5A2.25 2.25 0 0 1 21 11.25v7.5\" />\n </svg>\n Nächste Veranstaltung\n </span>\n <span className=\"stoerer-title\"><span>Effizienz durch n8n</span></span>\n <span className=\"stoerer-meta\">\n <time dateTime=\"2026-12-03\">3. Dezember 2026</time>\n <span aria-hidden=\"true\"> · </span>\n <span className=\"sr-only\">, </span>\n Dortmund\n </span>\n </a>\n </li>\n <li>\n <a className=\"stoerer\" href=\"#gt-hero-stoerer\">\n <span className=\"stoerer-topic\">\n <svg className=\"stoerer-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M12 7.5h1.5m-1.5 3h1.5m-7.5 3h7.5m-7.5 3h7.5m3-9h3.375c.621 0 1.125.504 1.125 1.125V18a2.25 2.25 0 0 1-2.25 2.25M16.5 7.5V18a2.25 2.25 0 0 0 2.25 2.25M16.5 7.5V4.875c0-.621-.504-1.125-1.125-1.125H4.125C3.504 3.75 3 4.254 3 4.875V18a2.25 2.25 0 0 0 2.25 2.25h13.5M6 7.5h3v3H6v-3Z\" />\n </svg>\n Neu im Wissen\n </span>\n <span className=\"stoerer-title\"><span>Refactoring-Schulden ehrlich rechnen</span></span>\n <span className=\"stoerer-meta\">\n <time dateTime=\"2026-04-22\">22. April 2026</time>\n <span aria-hidden=\"true\"> · </span>\n <span className=\"sr-only\">, </span>\n 7 min Lesezeit\n </span>\n </a>\n </li>\n <li>\n <a className=\"stoerer\" href=\"#gt-hero-stoerer\">\n <span className=\"stoerer-topic\">\n <svg className=\"stoerer-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M10.34 15.84c-.688-.06-1.386-.09-2.09-.09H7.5a4.5 4.5 0 1 1 0-9h.75c.704 0 1.402-.03 2.09-.09m0 9.18c.253.962.584 1.892.985 2.783.247.55.06 1.21-.463 1.511l-.657.38c-.551.318-1.26.117-1.527-.461a20.845 20.845 0 0 1-1.44-4.282m3.102.069a18.03 18.03 0 0 1-.59-4.59c0-1.586.205-3.124.59-4.59m0 9.18a23.848 23.848 0 0 1 8.835 2.535M10.34 6.66a23.847 23.847 0 0 0 8.835-2.535m0 0A23.74 23.74 0 0 0 18.795 3m.38 1.125a23.91 23.91 0 0 1 1.014 5.395m-1.014 8.855c-.118.38-.245.754-.38 1.125m.38-1.125a23.91 23.91 0 0 0 1.014-5.395m0-3.46c.495.413.811 1.035.811 1.73 0 .695-.316 1.317-.811 1.73m0-3.46a24.347 24.347 0 0 1 0 3.46\" />\n </svg>\n Pressemitteilung\n </span>\n <span className=\"stoerer-title\"><span>Conciso begleitet Stadtwerke bei der KI-Einführung</span></span>\n <span className=\"stoerer-meta\"><time dateTime=\"2026-08-12\">12. August 2026</time></span>\n </a>\n </li>\n <li>\n <a className=\"stoerer\" href=\"#gt-hero-stoerer\">\n <span className=\"stoerer-topic\">\n <svg className=\"stoerer-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" aria-hidden=\"true\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z\" />\n </svg>\n Neues Seminar\n </span>\n <span className=\"stoerer-title\"><span>Scrum Master Kurs, neue Termine ab Oktober</span></span>\n <span className=\"stoerer-meta\">Seminar · 2 Tage</span>\n </a>\n </li>\n </ul>\n</Unstyled>\n\n| Inhaltstyp | Thema-Label | Meta-Zeile | Ziel des Links |\n|---|---|---|---|\n| Veranstaltung | „Nächste Veranstaltung“ | Datum · Ort | Veranstaltungs-Detailseite |\n| Wissensbeitrag | „Neu im Wissen“ | Datum · Lesezeit | Wissensbeitrag |\n| Pressemitteilung | „Pressemitteilung“ | Datum | Presseseite oder einzelne Meldung |\n| Info | Spiegelt das Wort des Ziels, z. B. „Neues Seminar“, „Neues Produkt“ | Kurze Einordnung, z. B. Format und Dauer | Seminar-Landingpage, Produkt- oder Angebotsseite |\n\n```html\n<div class=\"stoerer-hero\">\n <figure class=\"hero-image\">…</figure>\n <aside class=\"stoerer-set\" aria-label=\"Aktuelles\">\n <ul class=\"stoerer-list\">\n <li>\n <a class=\"stoerer\" href=\"/veranstaltungen/effizienz-durch-n8n\">\n <span class=\"stoerer-topic\">\n <svg class=\"stoerer-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" aria-hidden=\"true\">…</svg>\n Nächste Veranstaltung\n </span>\n <span class=\"stoerer-title\"><span>Effizienz durch n8n</span></span>\n <span class=\"stoerer-meta\">\n <time datetime=\"2026-12-03\">3. Dezember 2026</time><span aria-hidden=\"true\"> · </span><span class=\"sr-only\">, </span>Dortmund\n </span>\n </a>\n </li>\n </ul>\n </aside>\n</div>\n```\n\n### Störer · Barrierefreiheit\n\n| Aspekt | Regel |\n|---|---|\n| Kontrast Thema | `--co-ink` auf `--bg-surface`: Light 5,52:1, Dark 8,32:1 |\n| Kontrast Titel | `--tx-primary`: Light 10,92:1, Dark 10,48:1 |\n| Kontrast Meta | `--tx-secondary`: Light 6,29:1, Dark 7,13:1, nicht `--tx-muted` |\n| Deckende Fläche | Text über Foto lässt sich nicht messen. Die Kachel ist deshalb opak, kein `backdrop-filter`, keine Alpha-Fläche |\n| Fokus | `--focus-ring` auf der ganzen Kachel plus `--e3`, ein Tab-Stop je Kachel, kein verschachtelter Link |\n| Hover ohne Farbe | Der Titel unterstreicht (WCAG 1.4.1), ein Schatten- oder Flächen-Zuwachs ist über einem Foto je nach Bildstelle kaum sichtbar |\n| Landmark | `<aside aria-label=\"Aktuelles\">` nach der Caption im DOM: Die H1 kommt zuerst, der Störer danach (WCAG 1.3.2) |\n| Trennzeichen | Der mittige Punkt in der Meta-Zeile ist `aria-hidden`, daneben steht ein `.sr-only`-Komma, sonst verschmelzen Datum und Ort |\n| Icon | `aria-hidden=\"true\"`, es wiederholt nur das Thema-Label und trägt keine eigene Bedeutung |\n\n### Störer · Verwendung\n\n| Aspekt | Regel |\n|---|---|\n| Anzahl | Default zwei, Maximum drei. Eine einzelne Kachel ist erlaubt, wenn nur ein Anlass aktuell ist |\n| Ein Typ, eine Kachel | Kein Typ doppelt im Set. Für mehrere Termine auf die Veranstaltungsübersicht (`Seitenmuster/Veranstaltungsübersicht`) verlinken |\n| Thema-Label | Ein bis drei Wörter, benennt den Inhaltstyp, nicht den Bereich |\n| Vokabular | Ein Domänenwort pro Domäne über Nav, Störer und Sektion |\n| Ziel | Die Detailseite des Inhalts, nie ein Abschnitt der Startseite. Existiert für einen Anlass keine eigene Seite, ist er kein Störer-Kandidat |\n| Dublette | Erlaubt und beabsichtigt: der Störer ist die Abkürzung zu einem Inhalt, der weiter unten seine Sektion hat. Bedingung: gleiches Ziel und gleiches Domänenwort, plus der Titel im zugänglichen Namen |\n| Titel | Der Titel des Inhalts, gekürzt auf das, was in zwei Zeilen trägt, redaktionell, nicht dem `line-clamp` überlassen |\n| Reihenfolge | Redaktionell frei, bewusst ohne Regel, aber sie wechselt nicht pro Seitenaufruf |\n| Aktualität | Ein Störer verfällt: vergangene Termine und Meldungen älter als das gewählte Redaktionsfenster raus |\n\n<div className=\"doc-eyebrow\">Dos & Don'ts</div>\n\n**✓ Tun**\n\n- Alle Kacheln eines Sets in gleicher Breite, gleichem Aufbau und gleicher Typo-Hierarchie halten, sie sind ein Set\n- Den Inhaltstyp über Thema-Label und Icon unterscheiden, die Farbgebung bleibt für alle Typen dieselbe\n- Die ganze Kachel als `<a>` auszeichnen, ein Tab-Stop, sichtbarer Hover und Fokus\n- Titel redaktionell auf zwei Zeilen kürzen, statt sich auf das `line-clamp` zu verlassen\n- Das Thema-Label mit dem Wort der zugehörigen Sektion bilden\n- Das Typ-Glyph als Leading-Element direkt vor sein Label setzen\n- Den Störer der Startseite vorbehalten, auf allen anderen Seiten trägt der Hero eine Aussage\n\n**✕ Nicht tun**\n\n- Kacheln in Bereichsfarben einfärben oder `data-area` setzen, dann konkurrieren sie untereinander und mit dem Hero\n- Halbtransparente Fläche oder `backdrop-filter`, der Kontrast hängt dann von der Bildstelle ab\n- Mehr als drei Kacheln, unterschiedlich große Kacheln, oder eine Kachel mit Bild und Anreißer (das ist dann eine Card und gehört in eine Sektion)\n- Buttons, einen zweiten Link oder ein Schließen-Kreuz in der Kachel, das bricht den einen Tab-Stop\n- Auf einen Anker der eigenen Seite verlinken statt auf die Detailseite\n- Das Glyph in den Trailing-Slot rechts stellen, mit oder ohne Trenner\n- Ein zweites Wort für dieselbe Domäne einführen\n- Das Overlay über eine Media Query an die Fensterbreite hängen statt über eine Container-Query an die Hero-Breite\n\n## Verwandte Seiten\n\n- `Komponenten/Hero/Hero-Bild` (`cds-hero-image`)\n- `Komponenten/Hero/Störer` (`cds-stoerer`, `cds-stoerer-set`)\n- `Komponenten/Slider & Carousel/Carousel` (Track-Mechanik des Slider-Hero)\n- `Komponenten/Cards & Teaser/Card` (16:9-Referenz gegenüber dem 21:9-Hero)\n- `Seitenmuster/Veranstaltung`, `Seitenmuster/Wissensbeitrag` (Ziele der Störer-Kacheln)\n- Doku-Sektion: `docs/index.html#sec-hero`\n",
13
+ "summary": "# Hero Drei Hero-Varianten für den Seitenkopf: Hero-Image mit Caption-Overlay (21:9, Stand..."
14
+ }
15
+ }
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "components": {
3
+ "komponenten-inputs-forms-textfeld": {
4
+ "id": "komponenten-inputs-forms-textfeld",
5
+ "name": "komponenten-inputs-forms-textfeld",
6
+ "docs": {
7
+ "komponenten-inputs-forms-textfeld--verwendung": {
8
+ "id": "komponenten-inputs-forms-textfeld--verwendung",
9
+ "name": "Verwendung",
10
+ "path": "./src/docs/komponenten/inputs-verwendung.mdx",
11
+ "title": "Komponenten/Inputs & Forms/Textfeld",
12
+ "content": "import { Meta } from '@storybook/addon-docs/blocks';\nimport * as TextFieldStories from '../../lib/field/text-field.stories';\n\n<Meta of={TextFieldStories} name=\"Verwendung\" />\n\n# Verwendung\n\nFeldkomponenten (Text · E-Mail · Select · Textarea · Checkbox · Radio), bereichsspezifische Kontaktformulare, das Such-Feld als Landmark, die Newsletter-Anmeldung (Karte und Kompakt-Widget) sowie der Slider. Durchgängig WCAG AA: Labels, `aria-required`, `aria-invalid`, `role=\"alert\"`.\n\n## Wann welche Variante\n\n### Feldkomponenten\n\n| Feld | Wann einsetzen |\n|---|---|\n| Text Input | Name, Firma, Betreff, alle einzeiligen Freitextangaben |\n| E-Mail Input | Immer `type=\"email\"` für automatische Formatvalidierung und Mobile-Keyboard |\n| Select | Vorgegebene Optionsliste (z. B. Bereich, Anliegen), max. 8 Optionen, sonst Freitext |\n| Textarea | Freitext-Nachrichten, mindestens 3 Zeilen Höhe, kein festes Maxlength |\n| Checkbox | Einwilligung (Newsletter, DSGVO), immer mit verlinktem Datenschutzhinweis als echtem `<a class=\"body-link\">` |\n| Radio | 2 bis 6 sich gegenseitig ausschließende Optionen (z. B. bevorzugter Kontaktweg), für mehr Optionen ein Select, für Mehrfachauswahl Checkboxen |\n| Segmented Control | Kompakte Radio-Variante für 2 bis 4 kurze, gleichrangige Optionen nebeneinander (`.seg`), bei längeren Labels oder Beschreibungen die Radio-Liste |\n\nDer Select-Platzhalter (erste Option „Bitte wählen…“) trägt `value=\"\"` plus `disabled` plus `selected`, damit er als unerlaubter Wert zählt: Browser-Validierung erkennt ein Pflicht-Select korrekt als leer, der Text wird nicht versehentlich beim Submit mitgesendet. Radio und Segmented Control bleiben immer **native** `<input type=\"radio\">`, kein nachgebauter Kreis, die Gruppe sitzt in einem `<fieldset>` mit `<legend>`, alle Optionen teilen denselben `name`. In einem Bereichsformular sind die Modifier `.seg-ki`, `.seg-es` und `.seg-wo` Pflicht, sonst sitzt mitten zwischen den Feldern ein Corporate-Akzent und das Formular trägt zwei Bereiche gleichzeitig.\n\n### Kontaktformulare je Bereich\n\nJedes Formular trägt die Akzentfarbe der Brand Area, Header und Submit-Button passen sich an.\n\n| Bereich | Header | Submit | Fokusring |\n|---|---|---|---|\n| Corporate | `--co-700` | `.btn-co` | Teal |\n| Angewandte KI | `--ki-800` | `.btn-ki` | Lime |\n| Effektive Software | `--es-700` | `.btn-es` | Blau |\n| Wirksame Organisationen | `--wo-700` | `.btn-wo` | Grün |\n\n### Adaptive Kontaktseite\n\nStatt eines eigenen Formulars pro Bereich gibt es eine Anfrageseite, die sich am Einstieg orientiert. Default ist die neutrale Corporate-Tönung, kommt die Nutzerin über einen Bereichs-CTA, übernimmt das Formular Farbe, Thema und ein vorbelegtes Anliegen. So bleibt die Seite auch für unentschlossene und bereichsübergreifende Nutzer richtig und vermeidet Near-Duplicate-Seiten.\n\nDer CTA trägt `data-ep=\"kontakt\"` plus optional `data-k-bereich=\"ki|es|wo\"` und `data-k-anliegen=\"…\"`. `applyKontaktContext()` tönt Header, Button und Consent-Häkchen, setzt das Thema-Select und belegt „Dein Anliegen“ vor.\n\n| Einstieg | Tönung | Thema | Anliegen (vorbelegt) |\n|---|---|---|---|\n| Topnav / Footer „Kontakt“ | co | Allgemeine Anfrage | (leer) |\n| AI.Automation · „Workshop anfragen“ | ki | Angewandte KI | AI.Automation Workshop |\n| Effektive Software · „Gespräch anfragen“ | es | Effektive Software | (leer) |\n| Wirksame Organisationen · „Transformation starten“ | wo | Wirksame Organisationen | Wirksame Organisationen: Transformation |\n\nZwei `<input type=\"hidden\">` transportieren den Herkunfts-Kontext stabil, unabhängig vom editierbaren Freitext: `name=\"bereich\"` (Werte `ki`, `es`, `wo`, `co`) ordnet an das richtige Team-Postfach zu, `name=\"unterthema\"` liefert die Basis für eine konkrete Erstantwort. Der Akzent hängt am Formular (`data-accent` am `<form>`), nicht an der ganzen Karte: Telefon, E-Mail und der Maps-Link daneben sind Unternehmens-Kontaktdaten und bleiben Corporate, unabhängig davon, aus welchem Bereich die Anfrage kommt. In Produktion werden diese Felder serverseitig aus Route oder Query-Parameter gerendert, nicht per Client-Skript.\n\n### Such-Feld\n\nPill-förmiges Such-Feld mit Magnifier-Icon links, zentriert auf Listing-Seiten. Kein sichtbares `<label>`: Icon und Placeholder tragen den visuellen Kontext, ein `aria-label` liefert den semantischen Namen für Screenreader. Struktur: Wrapper `<div role=\"search\">` mit eigenem `aria-label` (z. B. „Beiträge durchsuchen“) macht das Such-Feld zur Landmark, die Screenreader-Nutzerinnen direkt anspringen können.\n\n```html\n<div role=\"search\" aria-label=\"Beiträge durchsuchen\" style=\"position:relative\">\n <svg aria-hidden=\"true\" focusable=\"false\">…</svg>\n <input type=\"search\" aria-label=\"Beiträge nach Stichwort durchsuchen\"\n placeholder=\"Beitrag suchen\" autocomplete=\"off\">\n</div>\n```\n\nStil-Konventionen: Pill-Radius (`--r-full`), Border `1.5px solid var(--field-border)`, Hintergrund `--bg-surface`, Font `--ty-body-md`. `autocomplete=\"off\"` verhindert Browser-Vorschläge in einer Filterung.\n\n### Newsletter-Anmeldung\n\nZwei generische Varianten für Landingpages und Inline-Bereiche: eine eigenständige Karte und ein kompaktes Widget. Die Footer-Variante „Contentletter abonnieren“ ist ein eigenes Field-Pattern im Footer (eigene Klassen `.footer-newsletter-form`, `.footer-field`) und dort dokumentiert.\n\nAlle Varianten sprechen die Nutzerin in Du-Form an, das Name-Feld fragt explizit nach dem Vornamen, nicht dem vollen Namen, damit der Zweck klar ist: persönliche Anrede im Contentletter. Optional-Marker entfallen, weil die Required-Sternchen bereits Pflicht- und Optional-Felder trennen, eine zweite „(optional)“-Auszeichnung würde das Formular unnötig laut machen.\n\n| Aspekt | `.field` (Standard-Form) | `.footer-field` (Utility-Form) |\n|---|---|---|\n| Label-Token | `--ty-label-sm`, uppercase, `--tx-secondary` | Identisch |\n| Input-Border | `2px solid --field-border` | `1px solid --field-border` |\n| Input-Höhe | `min-height:44px` | `height:44px` |\n| Error-State | `.field.has-error` | `.footer-field.has-error` |\n\nLabels sind in beiden Patterns identisch (uppercase, `--ty-label-sm`, `--tx-secondary`), damit die Form-Sprache des Systems über Konversions- und Utility-Zonen hinweg konsistent bleibt.\n\n### Slider\n\nNumerische Wertauswahl mit nativer Tastatursteuerung (Pfeiltasten, Pos1, Ende). Basiert auf `<input type=\"range\">` mit verknüpftem `<label>` und semantisch korrektem `<output>`, der Browser setzt `role=\"slider\"`, `aria-valuemin/max/now` automatisch.\n\n```html\n<div class=\"field-slider\">\n <div class=\"field-slider-header\">\n <label class=\"field-slider-label\" for=\"my-slider\">Budget-Rahmen</label>\n <output class=\"field-slider-output slider-co\" for=\"my-slider\" id=\"my-out\">50.000 €</output>\n </div>\n <!-- aria-valuetext trägt Einheit/Format für Screenreader, valuenow allein wäre nur „50000“ -->\n <input class=\"slider slider-co\" type=\"range\" id=\"my-slider\"\n min=\"10000\" max=\"100000\" step=\"5000\" value=\"50000\"\n aria-describedby=\"my-hint\" aria-valuetext=\"50.000 €\">\n <span class=\"helper\" id=\"my-hint\">Schritte: 5.000 €</span>\n</div>\n```\n\n| Taste | Funktion |\n|---|---|\n| Pfeil links / Pfeil unten | Wert um 1 Schritt verringern |\n| Pfeil rechts / Pfeil oben | Wert um 1 Schritt erhöhen |\n| Pos1 | Minimalwert springen |\n| Ende | Maximalwert springen |\n\n## Barrierefreiheit\n\n| Attribut | Pflicht / Zweck |\n|---|---|\n| `label` | Jedes Feld braucht ein sichtbares Label, kein Placeholder als Ersatz |\n| `aria-required` | Pflichtfelder mit `aria-required=\"true\"` und sichtbarem `*` kennzeichnen |\n| `aria-invalid` | Bei Validierungsfehler `aria-invalid=\"true\"` setzen, Screenreader meldet Fehlerfeld |\n| `role=\"alert\"` | Fehlermeldung erhält `role=\"alert\"`, wird sofort vorgelesen, ohne Fokusverschiebung |\n| Link im Label | Verlinkte Rechtstexte in Einwilligungen (Datenschutz, Teilnahmebedingungen) sind echte `<a class=\"body-link\">`, kein `<span>` mit `cursor:pointer`, sonst nicht per Tab erreichbar und für Screenreader kein Link, obwohl dieser Text die Grundlage der Einwilligung ist. Im `<label for>` unkritisch: die Label-Aktivierung läuft bei interaktiven Nachfahren nicht, der Klick auf den Link setzt also kein Häkchen |\n| `autocomplete` | Passende Werte setzen (`name`, `email`, …), erleichtert Ausfüllen auf Mobile |\n\nFeld-Rahmen nutzen den modusbewussten Token `--field-border` mit mindestens 3 zu 1 gegen den Feld-Hintergrund (WCAG 1.4.11). Der Rahmen ist der einzige Umriss des Feldes, sobald es auf einer Karte sitzt, dort ist die Feldfüllung mit `--bg-surface` identisch mit dem Grund. Platzhalter-Text immer explizit auf `--tx-secondary` setzen, nie dem Browser überlassen, sonst unterschreitet die Browser-Standardfarbe im Dark Mode den AA-Kontrast.\n\n## Dos & Don'ts\n\n**Tun**\n\n- Jedes Feld mit sichtbarem Label versehen, Placeholder allein ist keine barrierefreie Beschriftung.\n- Fehlermeldungen direkt unter dem betroffenen Feld anzeigen, kontextnah und sofort lesbar.\n- Bereichsfarbe konsequent für Header und Submit-Button nutzen, das Formular gehört zum Bereichskontext.\n- Nur Pflichtfelder mit `*` markieren, unnötige Pflichtfelder reduzieren die Konversionsrate.\n- Genau ein Submit-Button pro Formular, klare Primäraktion, kein zweiter Filled-Button daneben.\n- Verlinkte Rechtstexte in Einwilligungen als echten `<a>` auszeichnen, damit sie per Tab erreichbar sind.\n\n**Nicht tun**\n\n- Placeholder als einzige Feldbeschriftung, verschwindet beim Tippen und ist nicht barrierefrei.\n- Disabled-Felder ohne Erklärung, die Nutzerin versteht nicht, warum das Feld nicht ausfüllbar ist.\n- Bereichsfarben in einem Formular mischen, jedes Formular gehört zu genau einer Brand Area.\n- Fehlermeldung nur per Farbe signalisieren, immer auch Text und Icon für farbenblinde Nutzende.\n- Mehr als 7 bis 8 Formularfelder ohne Abschnitte, lieber in Schritte oder Gruppen aufteilen.\n- Den Datenschutzhinweis im Einwilligungs-Label als `<span>` mit `cursor:pointer` bauen, er sieht aus wie ein Link und ist keiner.\n\n## Verwandte Seiten\n\n- Textfeld (`Komponenten/Inputs & Forms/Textfeld`)\n- Textbereich (`Komponenten/Inputs & Forms/Textbereich`)\n- Auswahlfeld (`Komponenten/Inputs & Forms/Auswahlfeld`)\n- Radio (`Komponenten/Inputs & Forms/Radio`)\n- Checkbox (`Komponenten/Inputs & Forms/Checkbox`)\n- Slider (`Komponenten/Inputs & Forms/Slider`)\n- Skala (`Komponenten/Inputs & Forms/Skala`)\n- Für Auswahl jenseits des nativen `<select>`: Custom Select und Combobox (`Komponenten/Dropdowns/Custom Select/Verwendung`)\n- Für die Footer-Newsletter-Variante: Footer (`Komponenten/Footer`)\n",
13
+ "summary": "# Verwendung Feldkomponenten (Text · E-Mail · Select · Textarea · Checkbox · Radio), berei..."
14
+ }
15
+ }
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "components": {
3
+ "komponenten-navigation-topnav": {
4
+ "id": "komponenten-navigation-topnav",
5
+ "name": "komponenten-navigation-topnav",
6
+ "docs": {
7
+ "komponenten-navigation-topnav--verwendung": {
8
+ "id": "komponenten-navigation-topnav--verwendung",
9
+ "name": "Verwendung",
10
+ "path": "./src/docs/komponenten/navigation-verwendung.mdx",
11
+ "title": "Komponenten/Navigation/Topnav",
12
+ "content": "import { Meta, Unstyled } from '@storybook/addon-docs/blocks';\nimport * as TopnavStories from '../../lib/topnav/topnav.stories';\n\n<Meta of={TopnavStories} name=\"Verwendung\" />\n\n# Verwendung\n\nNavigation führt drei Elemente: Topnav, Breadcrumb und Back-to-Top-Button. Nur die\nTopnav hat ein eigenes Bauteil (Story `Topnav`); Breadcrumb und Back-to-Top sind\nCSS-Rezepte ohne Angular-Komponente.\n\n## Wann welches Element\n\n| Element | Bauteil | Wann einsetzen |\n|---|---|---|\n| Topnav | Story `Topnav` | Auf jeder Customer-Page als persistente Kopfzeile: Logo, vier Top-Items, Kontakt-CTA |\n| Breadcrumb | bauteillos (CSS-Rezept `.article-breadcrumb`) | Auf Unterseiten ab der zweiten Ebene, zeigt den Pfad von der Startseite zur aktuellen Seite |\n| Back-to-Top-Button | bauteillos (CSS-Rezept `.back-to-top`) | Auf jeder Customer-Page, sticky unten rechts, erscheint ab `scrollY > 400px` |\n\n## Topnav: Aufbau\n\n| Element | Regel |\n|---|---|\n| Logo | Immer ganz links, immer sichtbar, Verlässlichkeit in der ersten Sekunde |\n| Top-Items | Vier Einträge: Angewandte KI · Leistungen · Wissen · Unternehmen. „Angewandte KI“, „Leistungen“ und „Unternehmen“ sind Links zur jeweiligen Übersicht mit aufklappbarem Submenü über einen separaten Caret-Button, „Wissen“ ist ein einfacher Link ohne Submenü. Aktiver Eintrag über `aria-current=\"page\"` |\n| CTA-Button | Genau ein Outlined-Button, Label „Kontakt“, Brand-Petrol (`--co-700`), ganz rechts über `margin-left:auto` |\n| Layout und Hintergrund | `.ep-topnav` mit `--bg-surface`, `border-bottom:var(--bd)`, Padding `var(--s4) var(--s8)` (rund 64 px hoch). Ein `gap`-basierter Flow (kein `justify-content`) hält die Nav am Logo, der CTA wird per `margin-left:auto` nach rechts gedrückt, das Layout bleibt stabil, egal wie viele Nav-Items dazukommen |\n\nDas Schema zeigt den Nav-Bar-Wireframe und darunter die zugehörige Callout-Zeile mit den\nnummerierten Hinweisen ①②③ zu Logo, Top-Items und Kontakt-Button.\n\n<Unstyled>\n <div style={{ border: 'var(--bd-strong)', borderRadius: 'var(--r-lg)', overflow: 'hidden', marginBottom: 'var(--s8)' }}>\n {/* Nav-Bar-Schema */}\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 'var(--s4)',\n padding: '0 var(--s6)',\n height: '64px',\n background: 'var(--bg-surface)',\n borderBottom: 'var(--bd)',\n }}\n >\n <div\n style={{\n background: 'var(--co-700)',\n borderRadius: 'var(--r-xs)',\n padding: '3px 10px',\n font: '700 12px/16px var(--font)',\n color: '#fff',\n flexShrink: 0,\n }}\n >\n Logo\n </div>\n <div style={{ display: 'flex', gap: 'var(--s5)', flex: 1, alignItems: 'center' }}>\n <div\n style={{\n display: 'inline-flex',\n alignItems: 'center',\n gap: 'var(--s1)',\n font: '400 12px/16px var(--font)',\n color: 'var(--tx-secondary)',\n }}\n >\n Angewandte KI <span style={{ opacity: 0.5 }}>▾</span>\n </div>\n <div\n style={{\n display: 'inline-flex',\n alignItems: 'center',\n gap: 'var(--s1)',\n font: '400 12px/16px var(--font)',\n color: 'var(--tx-secondary)',\n }}\n >\n Leistungen <span style={{ opacity: 0.5 }}>▾</span>\n </div>\n <div style={{ font: '400 12px/16px var(--font)', color: 'var(--tx-secondary)' }}>Wissen</div>\n <div\n className=\"t-co\"\n style={{\n display: 'inline-flex',\n alignItems: 'center',\n gap: 'var(--s1)',\n font: '600 12px/16px var(--font)',\n }}\n >\n Unternehmen <span style={{ opacity: 0.5 }}>▾</span>\n </div>\n </div>\n <div\n className=\"t-co\"\n style={{\n border: '1.5px solid var(--co-700)',\n borderRadius: 'var(--r-full)',\n padding: '2px 12px',\n font: '600 12px/16px var(--font)',\n flexShrink: 0,\n }}\n >\n Kontakt\n </div>\n </div>\n {/* Callout-Zeile zur Nav */}\n <div\n style={{\n display: 'flex',\n alignItems: 'flex-start',\n gap: 'var(--s4)',\n padding: 'var(--s3) var(--s6)',\n background: 'var(--bg-overlay)',\n }}\n >\n <div style={{ font: '400 12px/16px var(--font)', color: 'var(--tx-muted)', flexShrink: 0, width: '80px' }}>\n ① Logo<br />immer links\n </div>\n <div style={{ font: '400 12px/16px var(--font)', color: 'var(--tx-muted)', flex: 1 }}>\n ② Top-Items · 4 Einträge · „Angewandte KI“, „Leistungen“ und „Unternehmen“ mit aufklappbarem Submenü,\n „Wissen“ ohne · aktiver Eintrag in\n Brand-Petrol via <code className=\"token\" style={{ fontSize: '12px' }}>aria-current=\"page\"</code>\n </div>\n <div\n style={{ font: '400 12px/16px var(--font)', color: 'var(--tx-muted)', flexShrink: 0, textAlign: 'right' }}\n >\n ③ Outlined „Kontakt“<br />ganz rechts\n </div>\n </div>\n </div>\n</Unstyled>\n\n**Aktiv-Markierung:** Aktiver Punkt über `aria-current=\"page\"` ausgezeichnet und\nzusätzlich durch einen nicht-farbigen Indikator bestätigt: Top-Level-Links tragen einen\ndezenten Akzent-Unterstrich, Submenü-Einträge eine Tönungsfläche (`--co-50`) plus\nFettung. Die Markierung hängt also nie allein an der Farbe (WCAG 1.4.1). Nur Hover und\ndas bloße Aufklappen markieren ausschließlich über Farbe und Caret-Drehung, den\nUnterstrich gibt es allein im Aktiv-Zustand.\n\n## Topnav-Dropdowns: Disclosure-Pattern\n\nJedes Top-Item mit Submenü besteht aus zwei getrennten Bedienelementen: dem Label-Link\n(`.ep-nav-btn`, ein echtes `<a>`), der direkt zur Übersichtsseite navigiert, und einem\nseparaten Caret-Button (`.ep-nav-item-toggle`), der ausschließlich das Submenü öffnet\nund schließt. Es ist ein Disclosure-Pattern aus Links, kein Menü-Widget, deshalb bewusst\nohne `aria-haspopup`.\n\n- `aria-expanded` (`true`/`false`) und `aria-controls` sitzen auf dem Caret-Button; die JS-Klasse `.is-open` markiert das geöffnete `.ep-nav-has-sub`.\n- Geöffnet wird per Klick/Tap, Tastatur (`Enter`/`Space` auf dem Caret) oder, nur auf Geräten mit echtem Hover (`pointer:fine`), per Hover (JS-gesteuert mit Intent-Verzögerung und unsichtbarer Brücke über den Gap, WCAG 1.4.13).\n- Pfeiltasten (`↓`/`↑`) sowie `Home`/`End` navigieren die Einträge innerhalb des geöffneten Submenüs.\n- `Escape` schließt das Submenü und gibt den Fokus auf den Caret zurück; ein Klick außerhalb, das Heraustabben oder eine Navigation schließen ebenfalls.\n- **Kein reines CSS-`:hover`-Öffnen:** Der Reveal hängt immer an `.is-open`, damit `aria-expanded` mitläuft.\n- **Nie zwei Menüs gleichzeitig offen:** Vor jedem Öffnen ruft das JS `closeAllNavItems`.\n- Der Label-Link navigiert unverändert direkt zur Übersicht, nur der Caret-Button klappt auf; ein Klick auf das Label öffnet oder schließt kein Menü.\n- Mobil (ab 760 px und darunter): ein Hamburger-Button wird injiziert, Submenüs öffnen dort inline statt als schwebendes Overlay.\n- Der Caret-Button hat eine eigene Hit-Area ≥ 24 px (WCAG 2.5.8); mobil liegen Label und Caret in einer Zeile mit ≥ 44 px Tap-Fläche.\n\n## Breadcrumb\n\nAuf Unterseiten (zweite Ebene und tiefer) sitzt über dem Inhalt eine Breadcrumb-Leiste\n(`.article-breadcrumb`). Sie zeigt den Pfad von der Startseite bis zur aktuellen Seite;\ndas letzte Element ist die aktuelle Seite, trägt `aria-current=\"page\"` und ist kein\nLink.\n\n| Aspekt | Regel |\n|---|---|\n| Hülle | Eigene `.ep-section` mit `padding-block:var(--s5)` und `--bg-surface`, direkt unter der Topnav; das `<nav>` mit `margin-bottom:0` |\n| Ausrichtung | Immer linksbündig, auch auf redaktionellen Seiten (Wissensbeitrag, Stellen-Detail). Titel und Lead dürfen darunter zentriert sein, der Breadcrumb bleibt links |\n| Pfad | Startseite → … → aktuelle Seite; Trenner `.article-breadcrumb-sep` (dekorativ, `aria-hidden`); letztes Element `aria-current=\"page\"`, ohne Link |\n| Nicht | Den Breadcrumb nicht in einen zentrierten `.article-header` einbetten, sonst wird er mittig ausgerichtet und bekommt einen abweichenden Abstand zur Nav |\n\n## Back-to-Top-Button\n\nGlobaler Page-Button, der nach `scrollY > 400px` sanft eingeblendet wird und beim Klick\nsmooth zum Seitenanfang scrollt. Sitzt sticky unten rechts (`position:fixed`), damit er\nauf langen Seiten ohne Scrollen erreichbar bleibt; typischer Anwendungsfall:\nWissensbeiträge, Bereichsseiten, Listen-Übersichten. Gehört auf jede Customer-Page, auch\nLanding-Pages, Detailseiten und Beispielartikel.\n\n- `aria-label=\"Nach oben scrollen\"`, der reine Caret-Icon-Button braucht eine textuelle Beschriftung.\n- `type=\"button\"`, verhindert Submit-Verhalten in einem Form-Kontext.\n- Tastatur: per `Tab` erreichbar, `Enter`/`Space` löst aus, keine zusätzliche ARIA-Rolle nötig (natives `<button>`).\n- Reduced Motion: bei `prefers-reduced-motion: reduce` springt die Seite ohne Smooth-Scroll-Animation an den Anfang (WCAG 2.3.3).\n- Touch-Target: 44×44 px erfüllt WCAG 2.5.5 (Target Size).\n- Fokus-Ring: `var(--focus-ring)` bei Tastaturfokus, sichtbar gegen den weißen Hintergrund und gegen farbige Inhalte.\n\n## Barrierefreiheit\n\n| Aspekt | Regel |\n|---|---|\n| Semantik | `<nav>` mit `aria-label=\"Hauptnavigation\"`, wenn mehrere Navigationsbereiche auf einer Seite stehen, listen Screenreader alle Landmarks auf |\n| Aktiver Eintrag | `aria-current=\"page\"` auf dem aktiven Nav-Link setzen, der visuelle Active-State allein ist für Screenreader nicht erkennbar |\n| Tastatur | Alle Nav-Items per `Tab`; `Enter`/`Space` öffnet das Submenü; `↓`/`↑` und `Home`/`End` navigieren die Einträge; `Esc` schließt (Fokus zurück zum Toggle), vollständig ohne Maus bedienbar |\n| Dropdown-Zustand | Das Label ist ein `<a>`-Link zur Übersicht; ein separater Caret-`<button>` trägt per JS `aria-expanded` und `aria-controls` auf das Submenü (Disclosure aus Links, kein Menü-Widget, daher kein `aria-haspopup`); geöffnet wird per Klick/Tastatur und, additiv nur auf `pointer:fine`, per Hover (JS-gesteuert über `.is-open`, nie rein per CSS-`:hover`) |\n| Skip-Link | „Zum Inhalt springen“-Link als erstes fokussierbares Element, ermöglicht Tastaturnutzern das Überspringen langer Navigationen |\n\n## Dos & Don'ts\n\n**Tun**\n\n- Aktiven Eintrag über `aria-current=\"page\"` und zusätzlich einen nicht-farbigen Indikator markieren (Unterstrich beziehungsweise Tönungsfläche plus Fettung), nie nur über Farbe (WCAG 1.4.1)\n- Genau einen Outlined-Kontakt-Button ganz rechts, per `margin-left:auto` vom Nav-Flow abgesetzt\n- Label-Link und Caret-Button als getrennte Bedienelemente führen: Label navigiert, Caret klappt auf\n- Breadcrumb immer linksbündig setzen, auch wenn Titel und Lead darunter zentriert sind\n- Back-to-Top auf jeder Customer-Page einbinden, inklusive Fallback auf einen Hard-Jump bei `prefers-reduced-motion`\n\n**Nicht tun**\n\n- Dropdown-Menüs rein über CSS `:hover` öffnen, nicht per Tastatur erreichbar; `aria-expanded` für den Toggle nutzen\n- Vom IA-Schema (Angewandte KI · Leistungen · Wissen · Unternehmen plus Kontakt) abweichen oder zusätzliche Top-Items ergänzen, das verwässert die Customer-Page-Navigation\n- Breadcrumb in einen zentrierten `.article-header` einbetten, sonst verschiebt sich seine Ausrichtung\n- Zwei Submenüs gleichzeitig offen lassen, vor jedem Öffnen muss das vorherige schließen\n- Back-to-Top ohne `aria-label` oder ohne Fallback bei `prefers-reduced-motion` ausliefern\n\n## Verwandte Seiten\n\n- Komponenten/Navigation/Topnav\n- Komponenten/Hero (Zusammenspiel von Topnav und Hero)\n",
13
+ "summary": "# Verwendung Navigation führt drei Elemente: Topnav, Breadcrumb und Back-to-Top-Button. Nu..."
14
+ }
15
+ }
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "components": {
3
+ "komponenten-sektion-sektion": {
4
+ "id": "komponenten-sektion-sektion",
5
+ "name": "komponenten-sektion-sektion",
6
+ "docs": {
7
+ "komponenten-sektion-sektion--verwendung": {
8
+ "id": "komponenten-sektion-sektion--verwendung",
9
+ "name": "Verwendung",
10
+ "path": "./src/docs/komponenten/sektion.mdx",
11
+ "title": "Komponenten/Sektion/Sektion",
12
+ "content": "import { Meta } from '@storybook/addon-docs/blocks';\nimport * as SektionStories from '../../lib/section/section.stories';\n\n<Meta of={SektionStories} name=\"Verwendung\" />\n\n# Sektion\n\nDas strukturelle Gerüst, in dem auf den Beispielseiten praktisch jeder andere\nBaustein sitzt: `.ep-section` mit dem optionalen Kopf-Trio aus Kicker, Überschrift\nund Lead. 133 Vorkommen in den Beispielseiten, 85 davon mit Kopfzeile. Das\nAngular-Bauteil ist `Komponenten/Sektion/Sektion` (`[cdsSection]`).\n\nQuelle: `docs/index.html#sec-section`.\n\n## Aufbau\n\nDrei Klassen bilden den Kopf: `.ep-section-label` (Kicker, bereichsfärbbar über\n`.t-co` / `.t-ki` / `.t-es` / `.t-wo`), `.ep-section-h2` (Überschrift) und\n`.ep-section-sub` (Lead). Alle drei sind Beiwerk: Eine Sektion besteht auch ganz\nohne Kopf, nur aus ihrem Inhalt darunter.\n\n```html\n<div class=\"ep-section\" style=\"background:var(--n-50)\">\n <div class=\"ep-section-label t-co\">Was wir tun</div>\n <h2 class=\"ep-section-h2\">So ist eine Sektion aufgebaut.</h2>\n <p class=\"ep-section-sub\">Kicker, Überschrift und Lead sind Beiwerk, der Inhalt darunter ist frei.</p>\n <!-- beliebiger Inhalt -->\n</div>\n```\n\n## Angular-Wrapper\n\n`[cdsSection]` ist ein Attributselektor statt eines eigenen Elements\n(`docs/adr/0008-selektortyp-der-wrapper-komponenten.md`). Der\nKonsument wählt das Tag: `<section cdsSection>` oder `<div cdsSection>`. Die\nKomponente setzt `aria-labelledby`, wenn ein zugänglicher Name verfügbar ist:\nentweder die eigene Überschrift oder eine per `labelledBy` übergebene id einer\nÜberschrift außerhalb der Komponente. Fehlt beides, setzt sie kein\n`aria-labelledby`.\n\n## Verwendung\n\nDie Fläche gehört der Seite, nicht dem Bauteil. Sektionen wechseln im Rhythmus\nzwischen `--bg-surface` und `--n-50`, auf einzelnen Sektionsflächen kommt\nzusätzlich eine Bereichstönung als Akzent vor. Welche Fläche eine Sektion trägt,\nhängt von ihren Nachbarn ab, und nur die Seite kennt diese Nachbarn. Deshalb setzt\n`.ep-section` selbst keinen Hintergrund, und `[cdsSection]` hat keinen\n`background`-Input: Die Fläche kommt per Klasse oder Inline-Style direkt an das\nElement, das `cdsSection` trägt (`<section cdsSection style=\"background:…\">`).\n\n## Dos & Don'ts\n\n**Tun**\n\n- Die Hintergrundfläche als Klasse oder Style auf dem Element setzen, das `cdsSection` trägt, im Wechsel mit den Nachbar-Sektionen.\n- Kicker, Überschrift und Lead weglassen, wenn eine Sektion keinen eigenen Kopf braucht.\n- Ohne eigene Überschrift `labelledBy` auf eine vorhandene Überschrift setzen, damit die Sektion einen zugänglichen Namen behält.\n\n**Nicht tun**\n\n- Eine Hintergrundfarbe fest in die Komponente einbauen, sie kennt ihre Nachbar-Sektionen nicht.\n- Mehrere getönte Sektionen direkt hintereinander setzen, das wirkt schnell bunt statt ruhig.\n- Im Angular-Wrapper `<section cdsSection>` ohne Überschrift und ohne `labelledBy` schreiben, wenn `<div cdsSection>` genügt: die Sektion bliebe für Screenreader ohnehin namenlos, das Tag verspricht aber eine Landmark, die nie entsteht.\n",
13
+ "summary": "# Sektion Das strukturelle Gerüst, in dem auf den Beispielseiten praktisch jeder andere Ba..."
14
+ }
15
+ }
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "components": {
3
+ "komponenten-slider-carousel-carousel": {
4
+ "id": "komponenten-slider-carousel-carousel",
5
+ "name": "komponenten-slider-carousel-carousel",
6
+ "docs": {
7
+ "komponenten-slider-carousel-carousel--verwendung": {
8
+ "id": "komponenten-slider-carousel-carousel--verwendung",
9
+ "name": "Verwendung",
10
+ "path": "./src/docs/komponenten/slider-verwendung.mdx",
11
+ "title": "Komponenten/Slider & Carousel/Carousel",
12
+ "content": "import { Meta } from '@storybook/addon-docs/blocks';\nimport * as CarouselStories from '../../lib/carousel/carousel.stories';\n\n<Meta of={CarouselStories} name=\"Verwendung\" />\n\n# Verwendung\n\nSlider & Carousel führt zwei getrennte Formen: das Bild-Carousel zur Seiten-Integration\n(Story `Carousel`) und das Kundenlogo-Karussell (Story `LogoCarousel`) als CSS-Laufband.\nBeide sind WCAG 2.1 AA konform und respektieren `prefers-reduced-motion`. Die\nSlider-Hero-Variante für den Seitenkopf ist unter Komponenten/Hero dokumentiert.\n\n## Bild-Carousel · Verwendung\n\nEingebettet in Seiteninhalt, maximal 780 px breit, abgerundete Ecken (16:9). Caption\nerscheint unterhalb des Bildes auf weißem Grund, keine Modifier-Klasse nötig, nur die\nBasis-Klasse `.img-slider`. Übergang per Crossfade über 600 ms; unter\n`prefers-reduced-motion: reduce` entfällt die Transition automatisch.\n\n### Barrierefreiheit & ARIA\n\n| Attribut | Zweck |\n|---|---|\n| `role=\"region\"` | Semantischer Landmark für den gesamten Slider |\n| `aria-roledescription` | „Bildschirmpräsentation“, Screenreader liest den Typ vor |\n| `role=\"group\"` auf Slide | Fasst Bild und Caption je Folie zusammen |\n| `aria-hidden=\"true\"` | Inaktive Slides werden übersprungen (setzt JS) |\n| `aria-label` auf Button | „Vorherige / Nächste Folie“, kein Icon-only |\n| `role=\"tablist\"` Dots | Dot-Navigation als Tab-Gruppe, bekanntes Tastatur-Pattern |\n\n| Taste | Funktion |\n|---|---|\n| ← | Vorherige Folie |\n| → | Nächste Folie |\n| Tab | Fokus auf Prev/Next-Button oder Dot |\n| Enter / Space | Dot oder Button aktivieren |\n\n### Dos & Don'ts\n\n**Tun**\n\n- Immer aussagekräftiges `alt`-Attribut bei echten Bildern, kein `alt=\"\"` bei inhaltlichen Fotos\n- Caption für jede Folie, gibt dem Inhalt auch ohne Bild ausreichend Kontext\n- Bilder mit `object-fit: cover` und fester Höhe einsetzen, verhindert Layout-Sprünge beim Laden\n\n**Nicht tun**\n\n- Kein Auto-Play ohne Pause-Button, WCAG 2.1 Kriterium 2.2.2 verbietet unkontrollierte Bewegung\n- Mehr als 6 bis 7 Slides, Nutzende verlieren die Orientierung, lieber eine separate Galerie verwenden\n- Kartentypen oder Seitenverhältnisse innerhalb eines Carousels mischen, einheitliche Bildgröße halten\n\n## Kundenlogo-Karussell · Verwendung\n\nDiskrete Sets von je 5 Logos, die alle 6 Sekunden per Crossfade wechseln. Pausiert\nautomatisch bei Hover und Tastatur-Fokus, zusätzlich manueller Pause-Button (WCAG\n2.2.2). Pagination-Dots erlauben gezieltes Springen, Tastatur-Navigation über Pfeil-,\nHome- und End-Taste. Die Logos sitzen randlos auf einer hellen Platte (`--bg-plate`),\ndie in beiden Themes hell bleibt, weil Kundenlogos meist nur in einer farbigen\nbeziehungsweise dunklen Fassung ohne Dark-Variante vorliegen. Kein Hover-Effekt, da die\nLogos nicht verlinkt sind, ein Lift würde fälschlich Klickbarkeit suggerieren.\n\n### Barrierefreiheit\n\n| Attribut | Zweck |\n|---|---|\n| `role=\"region\"` + `aria-roledescription` | Semantischer Landmark mit Karussell-Hinweis für Screenreader |\n| `aria-hidden` | Inaktive Sets werden Screenreader-seitig ausgeblendet, nur das sichtbare Set wird vorgelesen |\n| `role=\"group\"` pro Set | Logos als Gruppe gebündelt mit eigenem `aria-label` („Set 1 von 2“) |\n| `role=\"tablist\"` + `role=\"tab\"` | Pagination-Dots als Tab-Pattern, `aria-selected` markiert das aktive Set, Roving-Tabindex |\n| `aria-pressed` auf Pause-Button | Toggle-State (pausiert / läuft) für Screenreader hörbar |\n| Tastatur-Navigation | Pfeil-Tasten wechseln Sets, Home / End springen zum ersten / letzten Set, Pause via Space oder Enter |\n| `prefers-reduced-motion` | Auto-Rotation und Crossfade deaktiviert, Set 1 bleibt statisch sichtbar, Dots bleiben nutzbar |\n\n### Dos & Don'ts\n\n**Tun**\n\n- Genau 5 Logos pro Set, gleiche Anzahl in allen Sets für gleichmäßiges Grid\n- Logos als SVG einbinden, skalieren verlustfrei auf jede Displaydichte\n- Einheitliche Tile-Höhe (72 px), unterschiedliche Logo-Proportionen über `object-fit: contain` angleichen\n- Pro Set eine eindeutige `id` für die `aria-controls`-Verknüpfung der Dots\n\n**Nicht tun**\n\n- Keine Kundennamen in bunten Markenfarben, Logos einheitlich grau oder schwarz halten\n- Mehr als 3 Sets vermeiden, ab 4 Sets wird die Pagination unübersichtlich und wirkt zu werblich\n- Intervall unter 5 Sekunden setzen, gibt Lesenden keine Zeit, ein Logo aufzunehmen\n\n## Bewegungs-Anforderung (prefers-reduced-motion)\n\nDie globale Barrierefreiheits-Sektion (Grundlagen/Barrierefreiheit) hält für\n`prefers-reduced-motion: reduce` die Basisregel fest: `animation-duration`,\n`transition-duration` und `scroll-behavior` fallen auf `0.01ms` beziehungsweise `auto`\nzurück (WCAG 2.3.3 AAA). Für die beiden Karussell-Typen gilt zusätzlich:\n\n- **Bild-Carousel:** die Crossfade-Transition (600 ms) entfällt automatisch, keine gesonderte Handhabung im Markup nötig.\n- **Kundenlogo-Karussell:** bei aktiver Reduktion bleibt das erste Set sichtbar, ohne Auto-Wechsel; die Pagination-Dots bleiben bedienbar.\n\nVon der Ausnahme unberührt bleibt der 2-Pixel-Hover-Lift auf klickbaren Karten und dem\nStörer: Er läuft auch unter `prefers-reduced-motion: reduce` weiter, weil er reine\nKlickbarkeit signalisiert. Auto-Bewegung ist davon getrennt zu betrachten, nur sie\nrespektieren die Carousels.\n\n## Verwandte Seiten\n\n- Komponenten/Slider & Carousel/Carousel\n- Komponenten/Slider & Carousel/LogoCarousel\n- Komponenten/Hero (Slider-Hero-Variante)\n- Grundlagen/Barrierefreiheit (Basisregel `prefers-reduced-motion`)\n",
13
+ "summary": "# Verwendung Slider & Carousel führt zwei getrennte Formen: das Bild-Carousel zur Seiten-I..."
14
+ }
15
+ }
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "components": {
3
+ "komponenten-tabelle-tabelle": {
4
+ "id": "komponenten-tabelle-tabelle",
5
+ "name": "komponenten-tabelle-tabelle",
6
+ "docs": {
7
+ "komponenten-tabelle-tabelle--verwendung": {
8
+ "id": "komponenten-tabelle-tabelle--verwendung",
9
+ "name": "Verwendung",
10
+ "path": "./src/docs/komponenten/tabelle.mdx",
11
+ "title": "Komponenten/Tabelle/Tabelle",
12
+ "content": "import { Meta } from '@storybook/addon-docs/blocks';\nimport * as TableStories from '../../lib/table/table.stories';\n\n<Meta of={TableStories} name=\"Verwendung\" />\n\n# Tabelle\n\nBarrierefreie Datentabellen mit `<caption>`, `scope`-Attributen und\nTastaturnavigation, in zwei Varianten: Standard und Gestreift, dazu eine\naufklappbare Vergleichstabelle. Standard und Gestreift deckt das Angular-Bauteil\n`Komponenten/Tabelle/Tabelle` (`cds-table`) ab: es liefert `.tbl-wrap` samt\nTastaturzugriff und Name sowie `.tbl`/`.tbl--striped`, der Konsument projiziert\n`<thead>`/`<tbody>`/`<tfoot>` unverändert hinein. Die aufklappbare Vergleichstabelle\ndeckt `Komponenten/Tabelle/Vergleichstabelle` (`cds-compare`) ab: anders als\n`cds-table` bekommt sie einen Daten-Input (`columns`/`rows`), weil ihre Zellen auf\nJa/Nein oder eine kurze Angabe begrenzt sind.\n\nQuelle: `docs/index.html#sec-table` (Zeilen 4979 bis 5208).\n\n## Standard\n\nZeilentrenner, Hover-Highlight, numerische Spalten rechtsbündig. Der\nScroll-Container ist per Tastatur fokussierbar (`tabindex=\"0\"`).\n\n```html\n<div class=\"tbl-wrap\" tabindex=\"0\" role=\"region\" aria-label=\"Leistungsübersicht Tabelle\">\n <table class=\"tbl\">\n <caption>Beratungsleistungen im Überblick</caption>\n <thead>\n <tr>\n <th scope=\"col\">Leistung</th>\n <th scope=\"col\">Format</th>\n <th scope=\"col\" data-num>Dauer (Tage)</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <th scope=\"row\">KI-Readiness Assessment</th>\n <td>Workshop &amp; Analyse</td>\n <td data-num>2</td>\n </tr>\n </tbody>\n </table>\n</div>\n```\n\nNumerische Spalten bekommen `data-num` an `<th>`/`<td>`, nicht eine eigene Klasse:\n`.tbl td[data-num]`/`.tbl th[data-num]` richten rechtsbündig aus und schalten auf\ntabellarische Ziffern (`font-variant-numeric: tabular-nums`) um.\n\n## Gestreift\n\nModifier `.tbl--striped` für Tabellen mit vielen Zeilen, verbessert die Lesbarkeit\nohne zusätzliche Rahmen.\n\n```html\n<div class=\"tbl-wrap\" tabindex=\"0\" role=\"region\" aria-label=\"Veranstaltungskalender Tabelle\">\n <table class=\"tbl tbl--striped\">\n <caption>Veranstaltungen &amp; Workshops 2025</caption>\n <thead>\n <tr>\n <th scope=\"col\">Datum</th>\n <th scope=\"col\">Veranstaltung</th>\n <th scope=\"col\">Status</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>15. Jan 2025</td>\n <th scope=\"row\">KI im Unternehmensalltag, Einstiegsworkshop</th>\n <td>Abgeschlossen</td>\n </tr>\n </tbody>\n </table>\n</div>\n```\n\nSortierbare Spaltenüberschriften nutzen `.tbl-sort` (ein `<button>` in der `<th>`)\nzusammen mit `aria-sort=\"ascending\"` bzw. `\"descending\"` auf der `<th>`; `.tbl--striped`\nund `.tbl-sort` lassen sich kombinieren, gestreift und sortierbar für\ndatenintensive Ansichten.\n\n## Vergleichstabelle (aufklappbar)\n\nFür den zeilenweisen Vergleich zweier oder mehrerer Pakete beziehungsweise Tarife.\nPreiskarten tragen Preis und drei bis vier Highlights (Konversion), die vollständige\nMerkmalsliste steckt in der aufklappbaren Tabelle (Tiefenvergleich auf Abruf). So\nbleibt der Schnell-Scan kurz, und Karten und Tabelle überschneiden sich nicht.\nErprobt auf der Beispielseite Angewandte KI, AI.Box (Leistungspakete).\n\nAufklappbar via nativem `<details>`/`<summary>` (`.ep-compare`, standardmäßig zu,\ngleiche Mechanik wie das Inhaltsverzeichnis in `Seitenmuster/Wissensbeitrag`).\nTabelle `.ep-compare-table` mit `<caption class=\"sr-only\">` und `scope`-Attributen.\nDie empfohlene Spalte wird über `.ep-compare-pro` (bereichsgetönt) hervorgehoben.\nEnthalten/Nicht enthalten als `.ep-compare-yes` (✓) beziehungsweise `.ep-compare-no`\n(−), jeweils mit `.sr-only`-Text, da Farbe und Glyph allein keine Bedeutung tragen\n(WCAG 1.4.1).\n\n```html\n<details class=\"ep-compare\">\n <summary class=\"ep-compare-summary\">Alle Funktionen vergleichen</summary>\n <div class=\"ep-compare-table-wrap\">\n <table class=\"ep-compare-table\">\n <caption class=\"sr-only\">Funktionsvergleich Core und Pro</caption>\n <thead>\n <tr>\n <th scope=\"col\">Funktion</th>\n <th scope=\"col\">Core</th>\n <th scope=\"col\" class=\"ep-compare-pro\">Pro</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <th scope=\"row\">Hosting in Deutschland</th>\n <td><span class=\"ep-compare-yes\" aria-hidden=\"true\">✓</span><span class=\"sr-only\">Enthalten</span></td>\n <td class=\"ep-compare-pro\">…</td>\n </tr>\n </tbody>\n </table>\n </div>\n</details>\n```\n\n## Verwendung\n\n<div className=\"doc-eyebrow\">Wann welche Variante?</div>\n\n| Variante | Wann einsetzen |\n|---|---|\n| Standard | Für Tabellen mit bis zu 6 Zeilen, klare Zeilentrenner reichen zur Orientierung |\n| Gestreift (`.tbl--striped`) | Ab ca. 7 Zeilen, der alternierende Hintergrund verbessert die Lesbarkeit ohne zusätzliche Rahmen |\n| Sortierbar (`.tbl-sort`) | Wenn Nutzer die Reihenfolge selbst bestimmen müssen, Klick auf die Spaltenüberschrift sortiert auf- oder absteigend |\n| Kombination | `.tbl--striped` und `.tbl-sort` lassen sich kombinieren, gestreift und sortierbar für datenintensive Ansichten |\n\n<div className=\"doc-eyebrow\">Barrierefreiheit</div>\n\n| Attribut / Element | Zweck |\n|---|---|\n| `<caption>` | Pflicht, Screenreader lesen den Titel vor, bevor die Zellen vorgelesen werden |\n| `scope=\"col\"` | Auf alle `<th>` im `<thead>`, ordnet Spaltenüberschriften den Zellen darunter zu |\n| `scope=\"row\"` | Wenn die erste Spalte eine Zeilenüberschrift ist, ordnet die Überschrift den Zellen rechts davon zu |\n| `tabindex=\"0\"` | Auf dem Scroll-Container, macht horizontales Scrollen per Tastatur erreichbar |\n| `role=\"region\"` + `aria-label` | Auf dem Scroll-Container, kennzeichnet ihn als benannten Landmark für Screenreader |\n\n<div className=\"doc-eyebrow\">Dos & Don'ts</div>\n\n**✓ Tun**\n\n- `<caption>` und `scope`-Attribute immer setzen, auch bei einfachen Tabellen\n- Numerische Spalten über `data-num` rechtsbündig ausrichten, erleichtert den Vergleich von Zahlen\n- Scroll-Container verwenden statt die Tabelle zu verkleinern, Lesbarkeit hat Vorrang\n- Spaltenüberschriften kurz und eindeutig benennen, maximal zwei bis drei Wörter\n\n**✕ Nicht tun**\n\n- Tabellen für Seiten-Layouts verwenden, dafür CSS Grid oder Flexbox nutzen\n- Mehr als sieben bis acht Spalten, lieber aufteilen oder Prioritäten setzen\n- Langen Fließtext in Zellen, Tabellen sind für strukturierte, scannbare Daten gedacht\n- Nur Farbe zur Bedeutungsvermittlung nutzen, Status-Badges zusätzlich mit Text kennzeichnen (siehe `Komponenten/Chips, Badges & Pills/Status-Badge`)\n\n## Verwandte Seiten\n\n- Doku-Sektion: `docs/index.html#sec-table`\n- `Komponenten/Tabelle/Tabelle` (Angular-Bauteil `cds-table` für Standard und Gestreift)\n- `Komponenten/Tabelle/Vergleichstabelle` (Angular-Bauteil `cds-compare` für die aufklappbare\n Vergleichstabelle)\n- `Komponenten/Chips, Badges & Pills/Status-Badge` (für Status-Zellen)\n",
13
+ "summary": "# Tabelle Barrierefreie Datentabellen mit ``, `scope`-Attributen und Tastaturnavigation, i..."
14
+ }
15
+ }
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "components": {
3
+ "komponenten-theme-umschalter-cycle-button": {
4
+ "id": "komponenten-theme-umschalter-cycle-button",
5
+ "name": "komponenten-theme-umschalter-cycle-button",
6
+ "docs": {
7
+ "komponenten-theme-umschalter-cycle-button--verwendung": {
8
+ "id": "komponenten-theme-umschalter-cycle-button--verwendung",
9
+ "name": "Verwendung",
10
+ "path": "./src/docs/komponenten/theme-umschalter.mdx",
11
+ "title": "Komponenten/Theme-Umschalter/Cycle-Button",
12
+ "content": "import { Meta } from '@storybook/addon-docs/blocks';\nimport * as CycleButtonStories from '../../lib/theme-switch/cycle-button.stories';\n\n<Meta of={CycleButtonStories} name=\"Verwendung\" />\n\n# Theme-Umschalter · Verwendung\n\n`docs/index.html` hat für den Theme-Umschalter eine eigene Sektion (`sec-theme`,\nÜberschrift „Theme-Umschalter“) mit einem `gt-theme-verwendung`-Block. Deren\nPlatzierungsregeln (welches der drei Bauteile wo eingesetzt wird) folgen hier\nnoch. Diese Seite beschreibt deshalb vor allem das Bauteil aus dem Repo selbst:\nder Angular-Lib\n(`angular-lib/projects/design-system-angular/src/lib/theme-switch/`) und der\nStorybook-Konfiguration (`.storybook/preview.ts`).\n\n## Dreistufiger Modus\n\n`CdsThemeMode` kennt drei Werte: `light`, `dark`, `system`. `system` folgt der\nBetriebssystem-Einstellung (`prefers-color-scheme`). Die Reihenfolge, in der alle\ndrei Umschalter zyklen beziehungsweise Optionen anbieten, ist fest:\n`CDS_THEME_ORDER = ['light', 'dark', 'system']`.\n\n| Modus | Label | Icon-Key |\n|---|---|---|\n| `light` | Hell | `heroSun` |\n| `dark` | Dunkel | `heroMoon` |\n| `system` | System | `heroComputerDesktop` |\n\nAlle drei Umschalter teilen denselben `showSystem`-Parameter: `true` zeigt alle drei\nModi (tri), `false` blendet „System“ aus und wechselt nur zwischen Hell und Dunkel\n(binär). Die Auswahl trifft `cdsThemeModes(showSystem)` in `theme-mode.ts`.\n\n## `themeStore` als einziger Schreiber von `data-theme`\n\nAlle drei Komponenten und die Storybook-Toolbar lesen und schreiben über\n`themeStore` (`theme-mode.ts`), eine modul-globale Quelle, kein Angular-Service:\nJede Story hat ihre eigene App-Instanz mit eigenem Root-Injector, Angular-DI allein\nwürde den Zustand also nicht über alle Stories hinweg teilen. `ThemeModeService` ist\nnur ein dünner Injectable-Wrapper darüber für `inject(ThemeModeService)` im\ngewohnten Muster.\n\n`themeStore` ist die **einzige** Stelle im System, die das Attribut `data-theme` am\n`<html>`-Element setzt oder entfernt:\n\n- `dark` gesetzt → `data-theme=\"dark\"`.\n- `light` → Attribut entfernt (Light ist der Default ohne Attribut).\n- `system` → koppelt live an `window.matchMedia('(prefers-color-scheme: dark)')`\n und reflektiert dessen `matches`-Wert als `dark` oder `light`; ein\n `change`-Listener hält das synchron, solange der Modus `system` bleibt.\n\nEin `document`/`window`-Guard schützt SSR und Prerendering: Ohne `document` wird\ndas Attribut übersprungen, der Modus bleibt im Signal erhalten und greift, sobald im\nBrowser das nächste `apply()` läuft. Die Lib fasst den globalen Cascade sonst nicht\nan (siehe `docs/adr/0001`).\n\n## Synchronisation mit der Storybook-Toolbar\n\n`.storybook/preview.ts` verdrahtet zwei Richtungen:\n\n1. **Store → Toolbar.** `themeStore.subscribe(...)` emittiert bei jeder Änderung\n `UPDATE_GLOBALS` an den Storybook-Channel, damit der globale\n Theme-Toolbar-Schalter mitwandert, wenn eine Story-Komponente den Modus ändert.\n2. **Toolbar → Store.** Ein Decorator ruft bei jedem Kontextwechsel\n `themeStore.setSilent(context.globals['theme'])` auf: `setSilent` wendet den\n Modus an, **ohne** erneut zu emittieren, sonst entstünde eine Rückkopplungs-\n schleife zwischen Toolbar und Store.\n\nDamit bleiben Toolbar, Docs-Vorschau und alle drei Switcher-Komponenten immer\ndenselben Modus zeigen. Das Docs-Chrome selbst (Überschriften, Tabellen) bleibt\ndavon unabhängig immer im Light-Theme (`concisoLight`), weil Storybook Toolbar- und\nDocs-Theme nicht koppelt.\n\n## Dark Mode: der Unterbau aus der CSS-Schicht\n\nDer CSS-Schicht-Vertrag (`docs/GETTING-STARTED.md` § 3) kennt nur ein Attribut:\n\n```js\ndocument.documentElement.setAttribute('data-theme', 'dark'); // dunkel\ndocument.documentElement.removeAttribute('data-theme'); // hell (Default)\n```\n\n**Genau zwei Modi in der ausgelieferten CSS.** Das System kennt Light und Dark,\nkeinen dritten Modus, der der Betriebssystem-Präferenz folgt: `prefers-color-scheme`\nwird im ausgelieferten CSS nicht ausgewertet, Default ist Light. Der dritte,\nUI-seitige Modus „System“ der drei Theme-Umschalter-Komponenten liegt deshalb eine\nEbene **über** der CSS-Schicht: `themeStore` löst `system` selbst über\n`matchMedia` auf und schreibt der CSS-Schicht am Ende immer nur einen der zwei\nZustände, die sie kennt (`data-theme=\"dark\"` gesetzt oder entfernt). Ein Consumer,\nder die Betriebssystem-Präferenz ohne diese Komponenten übernehmen will, wertet sie\nim eigenen Produkt genauso aus:\n\n```js\nif (matchMedia('(prefers-color-scheme: dark)').matches)\n document.documentElement.setAttribute('data-theme', 'dark');\n```\n\n**Anti-Flash-Snippet.** Als erstes Skript im `<head>`, vor dem CSS-Paint, damit beim\nReload nicht kurz Light aufblitzt. Liest die gespeicherte Präferenz aus\n`localStorage`:\n\n```html\n<script>\n (function(){\n try {\n var t = localStorage.getItem('ds-theme');\n if (t && t !== 'light') document.documentElement.setAttribute('data-theme', t);\n } catch(e) {}\n })();\n</script>\n```\n\nPersistenz beim Umschalten: `localStorage.setItem('ds-theme', 'dark' | 'light')`.\nDie drei Angular-Umschalter selbst persistieren nicht; das bleibt Aufgabe des\nConsumers, der `themeStore.subscribe(...)` an die eigene Persistenz anschließt.\n\n**Warum das für jede Komponente gilt (CONTRIBUTING § 5).** `data-theme` ist der\neinzige Schalter im System: Alle Token flippen darüber, keine Komponente wertet\n`prefers-color-scheme` selbst aus oder führt eine eigene dritte Flächen-Stufe ein.\nWeil `themeStore` der einzige Schreiber dieses Attributs ist, bleibt diese Garantie\nauch mit drei unterschiedlichen Umschalter-Bauteilen (Cycle-Button, Segment,\nDropdown) und der Storybook-Toolbar intakt: Es gibt genau eine Quelle der Wahrheit\nfür den Modus, unabhängig davon, über welchen Umschalter er gesetzt wurde.\n\n## Verwandte Seiten\n\n- `Komponenten/Theme-Umschalter/Cycle-Button` (Icon-Button, ein Klick wechselt reihum durch die Modi, vorgesehen für den Header)\n- `Komponenten/Theme-Umschalter/Segment` (Segment-Leiste zum Hovern, immer responsiv und animiert)\n- `Komponenten/Theme-Umschalter/Dropdown` (Custom Select, vorgesehen nur in den Einstellungen, nicht als persistentes Element)\n",
13
+ "summary": "# Theme-Umschalter · Verwendung `docs/index.html` hat für den Theme-Umschalter eine eigene..."
14
+ }
15
+ }
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "components": {
3
+ "komponenten-zitate-testimonials-blockquote": {
4
+ "id": "komponenten-zitate-testimonials-blockquote",
5
+ "name": "komponenten-zitate-testimonials-blockquote",
6
+ "docs": {
7
+ "komponenten-zitate-testimonials-blockquote--verwendung": {
8
+ "id": "komponenten-zitate-testimonials-blockquote--verwendung",
9
+ "name": "Verwendung",
10
+ "path": "./src/docs/komponenten/quotes-verwendung.mdx",
11
+ "title": "Komponenten/Zitate & Testimonials/Blockquote",
12
+ "content": "import { Meta } from '@storybook/addon-docs/blocks';\nimport * as BlockquoteStories from '../../lib/blockquote/blockquote.stories';\n\n<Meta of={BlockquoteStories} name=\"Verwendung\" />\n\n# Verwendung\n\nZitate & Testimonials führt vier Formen: Blockquote, Testimonial-Card, Team-Stimme und\ndas offene Zitat. Alle Formen teilen dasselbe semantische Grundgerüst\n(`<figure>` + `<blockquote>` + `<figcaption>`). Nur das offene Zitat hat kein eigenes\nBauteil, es ist ein CSS-Rezept ohne Karten-Chrome.\n\n## Wann welche Variante\n\n| Variante | Bauteil | Wann einsetzen |\n|---|---|---|\n| Blockquote | Story `Blockquote` | Kürzere Zitate (1 bis 3 Sätze, max. 200 Zeichen) im redaktionellen Kontext, eingebettet in Fließtext oder Abschnitte |\n| Testimonial-Card | Story `Testimonial` | Kundenreferenzen als eigenständige visuelle Einheit: Referenz-Sektionen, Hero-nahe Bereiche, Abschluss von Leistungsbeschreibungen, 2 bis 5 Sätze |\n| Team-Stimme | Story `TeamVoice` | Fotostarke, editoriale Variante der Testimonial-Card für Repräsentation (zum Beispiel Karriereseiten), volle Breite, großes Foto seitlich, abwechselnd links und rechts |\n| Offenes Zitat | bauteillos (CSS-Rezept, ohne Karten-Chrome) | Mehrere gleichrangige Stimmen in einer eigenen Sektion (zum Beispiel Workshop-Stimmen), Serif aufrecht statt kursiv, Trennung nur über Haarlinien |\n\n## Abgrenzung: Pull-Quote gegen Blockquote\n\nZwei Muster teilen sich dieselbe typografische Familie, aber nicht denselben Zweck. Ein\n**Pull-Quote** (`.article-pullquote`, dokumentiert unter Seitenmuster/Wissensbeitrag)\nhebt einen Satz aus dem eigenen Lauftext typografisch hervor: ohne Attribution, denn\ndie Autorin zitiert sich nicht selbst, sondern setzt einen Schlüsselsatz visuell ab. Der\n**Blockquote** (`.bq`, das Bauteil dieser Seite) ist dagegen ein wörtliches Zitat einer\ndritten Person (Kundin, Forschungsstimme, Branchen-Statement) mit Attribution (Name und\nRolle) und einer area-getönten Akzent-Box samt Quote-Icon. Beide teilen den\n4-Pixel-Linksakzent in der Bereichsfarbe; der Pull-Quote bleibt bewusst „offen“ (Serif\nkursiv, kein Hintergrund, kein Icon), der Blockquote ist umrahmt. Details zum Pull-Quote\nstehen unter Seitenmuster/Wissensbeitrag.\n\n## Barrierefreiheit\n\n| Aspekt | Regel |\n|---|---|\n| Semantik | `<blockquote>` korrekt für Zitate verwenden, Screenreader kündigen es als „Blockzitat“ an |\n| Dekoration | SVG-Anführungszeichen und Avatar-Initialen mit `aria-hidden=\"true\"` und `focusable=\"false\"` ausblenden |\n| Attribution | `<figcaption>` verknüpft die Quellenangabe semantisch mit dem Zitat, kein zusätzliches ARIA nötig |\n| Kontrast | Zitattext und Name in `--tx-primary`, Rolle in `--tx-secondary`, Avatar-Text geprüft ≥4,5:1 auf Bereichshintergrund in Light und Dark |\n| Anführungszeichen | Im Zitattext direkt eingebettet („…“), `quotes: none` verhindert doppelte Browser-Anführungszeichen |\n\n### Team-Stimme: zusätzliche Regeln\n\n| Aspekt | Regel |\n|---|---|\n| Semantik | `<figure>` + `<blockquote>` + `<figcaption>`, Quote-Icon dekorativ (`aria-hidden=\"true\"`, `focusable=\"false\"`) |\n| Bild-Alt | Beschreibendes `alt`, das keine konkrete Identität behauptet, solange Platzhalter- oder Stimmungsfotos genutzt werden |\n| Elevation | Statisch, daher flacher Rahmen (`--bd-strong`), kein Schatten, keine Klick-Affordanz |\n| Bereichs-Rahmen | Trägt das Bereichssignal und muss die 3:1-Schwelle für grafische Objekte erfüllen: Light `-600`/`-700`/`-500` (3,29 bis 5,66:1 auf Weiß), Dark `-300` (4,25 bis 9,48:1 auf der Karte). Vorher trug er mit `-200` im Light nur 1,27 bis 2,03:1 und mit `-800` im Dark 1,07 bis 1,63:1, war also in beiden Modi kein Signal |\n| Responsive | Unter 768 px stapelt das Grid zu Foto oben, Text unten |\n\n## Dos & Don'ts\n\n**Tun**\n\n- `<figure>` + `<blockquote>` + `<figcaption>` als semantisches Grundgerüst nutzen\n- Echte Kundenstimmen verwenden, keine KI-generierten Platzhalter in der Produktion\n- Bereichsfarbe über `data-area` steuern, keine hardcodierten Farbwerte\n- Zitate authentisch halten: gesprochene Sprache, konkrete Ergebnisse, keine Marketingfloskeln\n- Testimonial-Cards auf gleiche Höhe bringen: `align-items:stretch` im Grid plus `flex:1` auf dem Zitattext hält die Attribution am unteren Rand ausgerichtet\n\n**Nicht tun**\n\n- `<blockquote>` für eingerückte Aufzählungen oder rein visuell hervorgehobenen Text verwenden\n- Anführungszeichen doppelt setzen, entweder im Text oder via CSS `content`, nicht beides\n- Attribution weglassen, jedes Zitat braucht eine überprüfbare Quelle\n- Mehr als 3 Testimonials in einer Reihe, Lesbarkeit und visuelle Balance leiden\n- Dekorative Icons mit `role=\"img\"` oder `alt` versehen, sie sind `aria-hidden` und übermitteln keine Information\n\n## Verwandte Seiten\n\n- Komponenten/Zitate & Testimonials/Blockquote\n- Komponenten/Zitate & Testimonials/Testimonial\n- Komponenten/Zitate & Testimonials/TeamVoice\n- Seitenmuster/Wissensbeitrag (Abgrenzung Pull-Quote gegen Blockquote)\n",
13
+ "summary": "# Verwendung Zitate & Testimonials führt vier Formen: Blockquote, Testimonial-Card, Team-S..."
14
+ }
15
+ }
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "components": {
3
+ "marke-logo-logo": {
4
+ "id": "marke-logo-logo",
5
+ "name": "marke-logo-logo",
6
+ "docs": {
7
+ "marke-logo-logo--verwendung": {
8
+ "id": "marke-logo-logo--verwendung",
9
+ "name": "Verwendung",
10
+ "path": "./src/docs/marke/logo-verwendung.mdx",
11
+ "title": "Marke/Logo/Logo",
12
+ "content": "import { Meta, Unstyled } from '@storybook/addon-docs/blocks';\nimport * as LogoStories from '../../lib/logo/logo.stories';\n\n<Meta of={LogoStories} name=\"Verwendung\" />\n\n# Verwendung\n\nVerwendungsregeln zum Logo: Größen, Schutzraum, Einsatz nach Hintergrund, Barrierefreiheit und Dos & Don'ts. Die drei Varianten (Default, Light, Dark) zeigt die Story **`Marke/Logo/Logo`**; diese Seite ergänzt die Regeln dazu, inklusive des Hintergrund-Kontexts, den die Story allein nicht zeigt.\n\nDas Logo ist die primäre Markenkennung. Es wird stets als Vektor (SVG) verwendet, nie verzerrt oder eingefärbt.\n\n## Größen\n\nDie Höhe ist die maßgebliche Größenangabe, die Breite ergibt sich proportional aus dem Vektor (Verhältnis ≈ 7,6 : 1). Mindesthöhe nicht unterschreiten, sonst leidet die Lesbarkeit der „o.“-Form.\n\n| Kontext | Höhe | Hinweis |\n|---|---|---|\n| Topnav (Standard) | 24 px | Default-Variante, schaltet im Dark Mode auf Light um |\n| Footer | 32 px | Light-Variante, da Footer-Hintergrund `--n-700` immer dunkel ist |\n| Hero / Marketing-Header | 40 bis 48 px | Wenn das Logo selbst die Aufmerksamkeit tragen soll |\n| Mobile / kompakte Layouts | 20 px | Mindestgröße digital, darunter wird der Punkt schwer erkennbar |\n| Print | mindestens 12 mm | Mindestgröße im Druck, Punkt muss sauber drucken |\n\n## Schutzraum\n\nUm das Logo herum bleibt mindestens die Höhe des „C“ als freier Raum: keine konkurrierenden Elemente, kein Beschnitt durch andere Layout-Komponenten. Als Vielfaches ausgedrückt: die Referenzimplementierung der Doku-Site setzt ein Padding in Höhe des gesamten Logos (1:1-Clearance) an, eine praktikable Annäherung an dieselbe Regel.\n\nDas folgende Diagramm zeigt genau diese 1:1-Clearance: eine gestrichelte Box im Abstand einer vollen Logo-Höhe, hier 36 px, um die Wortmarke.\n\n<Unstyled>\n <div style={{ background: 'var(--bg-surface)', border: 'var(--bd-strong)', borderRadius: 'var(--r-lg)', padding: 'var(--s10)', display: 'flex', alignItems: 'center', justifyContent: 'center', marginBottom: 'var(--s8)' }}>\n {/* Padding 36px = exakte Logo-Image-Höhe (1:1-Clearance, Schutzraum-Regel der Brand Guidelines) */}\n <div style={{ position: 'relative', display: 'inline-block', padding: '36px' }}>\n <div style={{ position: 'absolute', inset: 0, border: '1px dashed var(--co-300)', borderRadius: 'var(--r-sm)', pointerEvents: 'none' }} />\n {/* Theme-abhängiges Logo: --bg-surface ist im Dark Mode dunkel, daher Default (dunkle Wortmarke) im Light, Light-Variante im Dark. Der Wechsel läuft über .logo-themed-default/.logo-themed-light (display:none/block), nicht über Inline-Styles. */}\n <img src=\"./conciso/brand/logo-conciso.svg\" alt=\"\" aria-hidden=\"true\" className=\"logo-themed-default\" style={{ height: '36px', width: 'auto', verticalAlign: 'middle' }} />\n <img src=\"./conciso/brand/logo-conciso-light.svg\" alt=\"\" aria-hidden=\"true\" className=\"logo-themed-light\" style={{ height: '36px', width: 'auto', verticalAlign: 'middle' }} />\n </div>\n </div>\n</Unstyled>\n\n## Verwendung\n\nDie Wahl der Variante richtet sich nach Hintergrund und Kontext. Im Zweifel die Default-Variante auf hellem, neutralem Untergrund verwenden.\n\nSo wirkt jede Variante auf dem Hintergrund, für den sie gedacht ist. Diesen Hintergrund-Kontext liefert die Story `Marke/Logo/Logo` allein nicht:\n\n<Unstyled>\n <div className=\"layout-grid\" style={{ marginBottom: 'var(--s8)' }}>\n <div className=\"col-4\" style={{ background: 'var(--bg-surface)', border: 'var(--bd-strong)', borderRadius: 'var(--r-lg)', overflow: 'hidden' }}>\n {/* --bg-plate: im Light reinweiß (der Anwendungsfall der Variante), im Dark gedämpft, damit die helle Platte auf der tiefen Seite nicht blendet. */}\n <div style={{ background: 'var(--bg-plate)', padding: 'var(--s8) var(--s6)', display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '120px' }}>\n <img src=\"./conciso/brand/logo-conciso.svg\" alt=\"Conciso, Default-Variante\" style={{ height: '36px', width: 'auto', display: 'block' }} />\n </div>\n <div style={{ padding: 'var(--s5) var(--s6)', borderTop: 'var(--bd)' }}>\n <div className=\"doc-eyebrow\" style={{ marginBottom: 'var(--s2)' }}>Default</div>\n <div style={{ font: 'var(--ty-title-sm)', color: 'var(--tx-primary)', marginBottom: 'var(--s2)' }}>Vollfarbe</div>\n <p style={{ font: 'var(--ty-body-md)', color: 'var(--tx-secondary)', margin: 0 }}>Wortmarke in <code className=\"token\">--tx-brand</code> mit teal-Punkt in <code className=\"token\">--co-500</code>. Standard für helle Hintergründe.</p>\n </div>\n </div>\n <div className=\"col-4\" style={{ background: 'var(--bg-surface)', border: 'var(--bd-strong)', borderRadius: 'var(--r-lg)', overflow: 'hidden' }}>\n <div style={{ background: 'var(--n-700)', padding: 'var(--s8) var(--s6)', display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '120px' }}>\n <img src=\"./conciso/brand/logo-conciso-light.svg\" alt=\"Conciso, helle Variante\" style={{ height: '36px', width: 'auto', display: 'block' }} />\n </div>\n <div style={{ padding: 'var(--s5) var(--s6)', borderTop: 'var(--bd)' }}>\n <div className=\"doc-eyebrow\" style={{ marginBottom: 'var(--s2)' }}>Light</div>\n <div style={{ font: 'var(--ty-title-sm)', color: 'var(--tx-primary)', marginBottom: 'var(--s2)' }}>Weiß</div>\n <p style={{ font: 'var(--ty-body-md)', color: 'var(--tx-secondary)', margin: 0 }}>Vollständig weiß, für dunkle Hintergründe (Footer, Hero-Gradients, Dark Mode).</p>\n </div>\n </div>\n <div className=\"col-4\" style={{ background: 'var(--bg-surface)', border: 'var(--bd-strong)', borderRadius: 'var(--r-lg)', overflow: 'hidden' }}>\n {/* Panel-Hintergrund fest hell (#F5F7F7), nicht das theme-reaktive Token: das Monochrom-Logo ist fix in --tx-brand gezeichnet und bräuchte im Dark Mode sonst dunkel-auf-dunkel. */}\n <div style={{ background: '#F5F7F7', padding: 'var(--s8) var(--s6)', display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '120px' }}>\n <img src=\"./conciso/brand/logo-conciso-dark.svg\" alt=\"Conciso, monochrom-dunkle Variante\" style={{ height: '36px', width: 'auto', display: 'block' }} />\n </div>\n <div style={{ padding: 'var(--s5) var(--s6)', borderTop: 'var(--bd)' }}>\n <div className=\"doc-eyebrow\" style={{ marginBottom: 'var(--s2)' }}>Dark</div>\n <div style={{ font: 'var(--ty-title-sm)', color: 'var(--tx-primary)', marginBottom: 'var(--s2)' }}>Monochrom</div>\n <p style={{ font: 'var(--ty-body-md)', color: 'var(--tx-secondary)', margin: 0 }}>Komplett in <code className=\"token\">--tx-brand</code>, ohne teal-Punkt. Für Druck, Stempel, einfarbige Anwendungen oder wenn der Akzent visuell stört.</p>\n </div>\n </div>\n </div>\n</Unstyled>\n\n| Hintergrund | Variante | Begründung |\n|---|---|---|\n| Weiß / hell-neutral | Default | Volle Brand-Identität mit teal-Akzent |\n| Dunkel (mindestens 60 % Schwarzanteil) | Light | Vollständig weiß für maximale Lesbarkeit |\n| Bereichsfarbe (`--co/ki/es/wo-500`) | Light oder Dark | Default vermeiden, der teal-Punkt konkurriert mit der Bereichsfarbe |\n| Foto / unruhiger Hintergrund | Light auf Scrim | Halbtransparente dunkle Fläche unter Logo legen, dann Light verwenden |\n| Druck monochrom, Stempel, Embossing | Dark | Kein Akzent verfügbar, komplett einfarbig |\n| Dark-Mode-Interfaces | Light (auto-swap) | Wechsel via `[data-theme=\"dark\"]`-Selektor |\n\n### Barrierefreiheit\n\n| Aspekt | Regel |\n|---|---|\n| Alt-Text | Inhaltlich tragend (zum Beispiel Footer-Brand): `alt=\"Conciso\"`. Dekorativ daneben (zum Beispiel Topnav-Wortmarke neben Text-Logo): `alt=\"\"` mit `aria-hidden=\"true\"` |\n| Kontrast | Mindestens 3:1 zwischen Logo und Hintergrund (WCAG 1.4.11, Non-Text Contrast). Default auf Weiß: 11,7:1. Light auf `--n-700`: 15,3:1 |\n| Theme-Wechsel | Logo-Swap via CSS und `[data-theme=\"dark\"]`-Selektor, funktioniert ohne JS und respektiert die Nutzer-Präferenz |\n| Skalierung | SVG ist auflösungsunabhängig, bleibt scharf bei beliebigem Browser-Zoom (WCAG 1.4.4) |\n\n### Dos & Don'ts\n\n✓ Tun\n\n- Default-Variante auf hellen, neutralen Hintergründen: der teal-Punkt verankert die Brand-Identität.\n- Light-Variante auf dunklen Hintergründen ab `--n-700` oder Hero-Gradients.\n- Dark-Variante (monochrom) für Druck, Stempel oder wo der teal-Punkt visuell stört.\n- Schutzraum von mindestens der „C“-Höhe um das Logo herum freihalten.\n- Logo proportional skalieren (Höhe als Leitmaß), der Vektor bleibt in jeder Größe scharf.\n- Theme-Swap nutzen: `.logo-themed-default` + `.logo-themed-light`, schaltet automatisch via `[data-theme]`.\n\n✕ Nicht tun\n\n- Logo stauchen, dehnen, rotieren oder schief stellen: immer proportional und gerade.\n- Den teal-Punkt durch andere Bereichsfarben ersetzen (KI-Lime, ES-Blau, WO-Grün), Default ist immer `--co-500`.\n- Default-Variante auf dunklem Hintergrund einsetzen: die dunkle Schrift verschwindet, der teal-Punkt wird zur isolierten Insel.\n- Logo unter 20 px Höhe digital oder 12 mm im Druck verwenden, der Punkt zerfällt.\n- Logo als PNG oder JPG einbinden, immer SVG verwenden, damit es scharf bleibt und der Theme-Swap funktioniert.\n- Logo als dekoratives Element in Texten, Mustern oder Hintergrundgrafiken einsetzen, es bleibt eigenständig.\n",
13
+ "summary": "# Verwendung Verwendungsregeln zum Logo: Größen, Schutzraum, Einsatz nach Hintergrund, Bar..."
14
+ }
15
+ }
16
+ }
17
+ }
18
+ }
@@ -4,7 +4,7 @@
4
4
  "id": "komponenten-call-to-action-downloadcta",
5
5
  "name": "DownloadCtaComponent",
6
6
  "path": "./src/lib/download-cta/download-cta.stories.ts",
7
- "description": "DownloadCta — Wrapper um `.cta-dl` aus css/components.css → „Download CTA“.\n\nAufmerksamkeitsstarker Download-Block mit bereichsgefärbtem Top-Akzent + Icon\n(data-area), Eyebrow/Titel/Beschreibung/Meta und zwei Aktionen (primär/sekundär,\nüber die bestehenden `.btn`-Klassen). Konsumiert nur vorhandene Styles.",
7
+ "description": "DownloadCta — Wrapper um `.cta-dl` aus css/components.css → „Download CTA“.\n\nAufmerksamkeitsstarker Download-Block mit bereichsgefärbtem Top-Akzent + Icon\n(data-area), Eyebrow/Titel/Beschreibung/Meta und zwei Aktionen (primär/sekundär,\nüber die bestehenden `.btn`-Klassen). Konsumiert nur vorhandene Styles.\n\nVerwendungsguidance dieser Gruppe: siehe CTA-Band (`komponenten-call-to-action-cta-band--verwendung`).",
8
8
  "jsDocTags": {},
9
9
  "argTypes": {
10
10
  "area": {
@@ -4,7 +4,7 @@
4
4
  "id": "komponenten-cards-teaser-feature-liste",
5
5
  "name": "FeatureComponent",
6
6
  "path": "./src/lib/feature/feature.stories.ts",
7
- "description": "Feature (`[cdsFeature]`) — Wrapper um `.ep-feature` aus css/components.css\n(css/components.css:1363–1379): die flache Feature-Zeile ohne Box und ohne\nSchatten (Icon-Kachel links, Titel/Text rechts) — die „Offene Feature-Liste“ der\nBeispielseiten (Doku-Sektion `docs/index.html:4882` ff., Beispielseiten u. a.\n`docs/index.html:10405–10415`, `11582–11588`; 40 Vorkommen als\n`class=\"ep-feature col-4\"` im `.layout-grid`).\n\n**Attributselektor, kein eigenes Element (ADR-0008).** Das Mockup setzt die\nSpaltenklasse am selben Element wie `.ep-feature` (`class=\"ep-feature col-4\"`).\nDas Bauteil ist damit selbst Grid-Kind UND Ziel einer Layout-Klasse des\nKonsumenten — exakt das Kriterium aus\n[ADR-0008](../../../../../../docs/adr/0008-selektortyp-der-wrapper-komponenten.md).\nEin eigenes `<cds-feature>`-Element mit innerem `<div class=\"ep-feature\">` würde\n`.layout-grid>[class*=\"col-\"]` (css/base.css:214) brechen: der `col-*`-Selektor\nträfe den unsichtbaren Host, nicht `.ep-feature`, das `.layout-grid` gibt seinem\nKind (dem Host) dann zwar per `align-items:stretch` (Grid-Default) die volle\nZeilenhöhe, aber `.ep-feature` darunter bliebe auf Inhaltshöhe — derselbe Fehler,\nden ADR-0008 Fall 1 für `.ep-card`/`.ep-cards` gemessen hat (174px/270px statt\n306px/306px). Die Komponente hängt sich deshalb als Attribut an ein vom\nKonsumenten geschriebenes `<div>` (`<div cdsFeature class=\"col-4\">`), analog zu\n`cds-icon-card` (siehe dessen Klassendoku). Gemessen für dieses Bauteil in der\nStory „Dreispalter“ (Play-Funktion): drei `.ep-feature` mit stark\nunterschiedlich langem Text als `col-4`-Geschwister in einem `.layout-grid`\nergeben identische Höhen und deckungsgleiche Unterkanten (Messwerte in der\nStory-Doku dort).\n\n**Icon-Kontrakt: `.ep-feature-icon` ist ein Container, keine Klasse auf dem\nSVG — UND reicht zusätzlich die Farbe durch, anders als `.ep-card-icon`.** Wie\n`.ep-card-icon` (siehe `icon-card.component.ts`, Entscheidung 2) setzt\ncss/components.css:1369 Maße und Stroke über den Nachfahren-Selektor\n`.ep-feature-icon svg{width:26px;height:26px;stroke-width:var(--icon-stroke-md)}`\n— NICHT über eine Klasse auf dem SVG selbst, anders als `.stoerer-icon`\n(`stoerer.component.ts`), das direkt auf dem projizierten `<svg>` sitzt. Der\nKonsument liefert deshalb ein unverändertes\n`<svg cdsIcon viewBox=\"…\" aria-hidden=\"true\" focusable=\"false\">…</svg>` OHNE\nzusätzliche Größenklasse, projiziert über `<ng-content select=\"[cdsIcon]\">`\n(Kontrakt in der Story „Interaktiv“ per `getBoundingClientRect()` gepinnt).\nAnders als `.ep-card-icon` (nur `background`, kein `color`) setzt\n`.ep-feature-icon` UND ihre vier `[data-area]`-Varianten (css/components.css:\n1367–1372) zusätzlich `color` auf dem Container selbst. Das Mockup nutzt das\naktiv aus: die projizierten SVGs dort (`docs/index.html:10406` u. a.) tragen\n`stroke=\"currentColor\"` statt eines hart codierten Bereichstons — die Farbe\nkommt allein aus dem Container, ein Icon-Autor muss den Bereich der Kachel gar\nnicht kennen. Bei `cds-icon-card` ist das nicht möglich (kein `color` am\nContainer), dort trägt jedes Icon seinen Bereichston selbst im `stroke`-Attribut.\nStory „Interaktiv“ demonstriert das mit `stroke=\"currentColor\"` und prüft die\ngeerbte Farbe per `getComputedStyle()`. Die Icon-Kachel selbst ist im Mockup\n(`docs/index.html:4883` u. a.) ein `<span aria-hidden=\"true\">`, kein `<div>` —\nrein dekorativ, das SVG trägt bereits sein eigenes `aria-hidden`; die Komponente\nübernimmt beides unverändert.\n\n**CTA: `<a>` mit `href`, sonst `<span>` — nie ein `<a>` ohne `href`.**\ncss/components.css:1376 zielt mit `.ep-feature-body>.card-cta-link` auf den\ndirekten Nachfahren; Angulars `@if`/`@else` fügt dafür kein Wrapper-Element ein\n(kompiliert zu Kommentar-Ankern, kein Element), der Kindselektor bleibt also in\nbeiden Zweigen erhalten. Ausgezählt in `docs/index.html`: alle 13 Vorkommen von\n`.card-cta-link` innerhalb von `.ep-feature-body` sind entweder ein `<a\nhref=\"…\">` (12×) oder ein `<span>` (1×, `docs/index.html:13216`,\n„Landingpage folgt“ — Platzhalter für ein Ziel, das noch fehlt). Ein `<a>` OHNE\n`href` kommt kein einziges Mal vor — dieselbe Falle, die `cds-icon-card` über\n`isLink()` schließt (ein Link-Tag ohne `href` ist weder fokussierbar noch hat\nes eine Link-Rolle, sieht mit der `.card-cta-link`-Optik aber trotzdem wie ein\nbedienbares Element aus). Hier entscheidet sich das nicht am Host-Tag (der ist\nbei `cdsFeature` immer `<div>`), sondern strukturell im eigenen Template:\n`ctaHref` gesetzt → `<a class=\"card-cta-link\" [href]=\"ctaHref()\">`, leer →\n`<span class=\"card-cta-link\">` mit identischer Optik und identischem Pfeil,\naber ohne Link-Rolle (siehe Story „Ohne Href“). Ein `[attr.href]`-Nullwert wäre\ndieselbe Krücke gewesen wie ein `<a>` ohne `href` zu tolerieren; der\n`@if`/`@else`-Zweig macht den Fall stattdessen unmöglich, statt ihn nur\nabzufedern. `data-area` sitzt auf beiden Zweigen, weil `.card-cta-link[data-area]`\n(css/components.css:1363–1366) ein direkter Attributselektor ist, keine\nNachfahren-Regel wie `.ep-card[data-area] .ep-card-cta` bei der Icon-Karte —\nohne eigenes `data-area` bliebe der Link/Span auf der CSS-Vorgabefarbe, auch bei\ngesetztem `area`.\n\n**`ctaAriaLabel` — der zugängliche Name ist im Mockup die Regel, nicht die\nAusnahme.** Erster Anlauf dieser Komponente hatte keinen solchen Input, mit der\n(falschen) Annahme, das Mockup verzichte meist auf einen zusätzlichen\n`aria-label`. Ausgezählt: von den 12 `<a class=\"card-cta-link\">` in\n`.ep-feature-body` tragen 11 einen `aria-label`, nur einer nicht\n(„Contentletter abonnieren“, `docs/index.html:15203` — der sichtbare Text ist\ndort bereits eindeutig). Grund: nur 3 verschiedene sichtbare Texte verteilen\nsich auf diese 12 Links, „Zur Landingpage“ allein zehnmal\n(`docs/index.html:11587` u. a.) — eine Screenreader-Linkliste hörte sonst\nzehnmal denselben Namen ohne Unterscheidung. Das Mockup disambiguiert\ndurchgängig mit der Form `<sichtbarer Text>: <Ziel>`\n(`aria-label=\"Zur Landingpage: KI Kickstart Workshops\"`). `ctaAriaLabel` ist\ndeshalb Beiwerk (Default `''`): leer bleibt der sichtbare `ctaLabel`-Text der\nzugängliche Name (passt für einen eindeutigen CTA-Text wie „Contentletter\nabonnieren“), gesetzt überschreibt er ihn — Konsumenten mit einer Liste\ngleichlautender CTA-Texte (wie den zehn „Zur Landingpage“ im Mockup) setzen ihn\npro Instanz auf `<ctaLabel>: <Ziel>`. Wirkt nur am `<a>`-Zweig: `aria-label` an\neinem `<span>` ohne Rolle hat keinen verlässlichen Effekt im\nAccessibility-Baum, und der `<span>`-Zweig ist ohnehin nicht interaktiv.",
7
+ "description": "Feature (`[cdsFeature]`) — Wrapper um `.ep-feature` aus css/components.css\n(css/components.css:1363–1379): die flache Feature-Zeile ohne Box und ohne\nSchatten (Icon-Kachel links, Titel/Text rechts) — die „Offene Feature-Liste“ der\nBeispielseiten (Doku-Sektion `docs/index.html:4882` ff., Beispielseiten u. a.\n`docs/index.html:10405–10415`, `11582–11588`; 40 Vorkommen als\n`class=\"ep-feature col-4\"` im `.layout-grid`).\n\n**Attributselektor, kein eigenes Element (ADR-0008).** Das Mockup setzt die\nSpaltenklasse am selben Element wie `.ep-feature` (`class=\"ep-feature col-4\"`).\nDas Bauteil ist damit selbst Grid-Kind UND Ziel einer Layout-Klasse des\nKonsumenten — exakt das Kriterium aus\n[ADR-0008](../../../../../../docs/adr/0008-selektortyp-der-wrapper-komponenten.md).\nEin eigenes `<cds-feature>`-Element mit innerem `<div class=\"ep-feature\">` würde\n`.layout-grid>[class*=\"col-\"]` (css/base.css:214) brechen: der `col-*`-Selektor\nträfe den unsichtbaren Host, nicht `.ep-feature`, das `.layout-grid` gibt seinem\nKind (dem Host) dann zwar per `align-items:stretch` (Grid-Default) die volle\nZeilenhöhe, aber `.ep-feature` darunter bliebe auf Inhaltshöhe — derselbe Fehler,\nden ADR-0008 Fall 1 für `.ep-card`/`.ep-cards` gemessen hat (174px/270px statt\n306px/306px). Die Komponente hängt sich deshalb als Attribut an ein vom\nKonsumenten geschriebenes `<div>` (`<div cdsFeature class=\"col-4\">`), analog zu\n`cds-icon-card` (siehe dessen Klassendoku). Gemessen für dieses Bauteil in der\nStory „Dreispalter“ (Play-Funktion): drei `.ep-feature` mit stark\nunterschiedlich langem Text als `col-4`-Geschwister in einem `.layout-grid`\nergeben identische Höhen und deckungsgleiche Unterkanten (Messwerte in der\nStory-Doku dort).\n\n**Icon-Kontrakt: `.ep-feature-icon` ist ein Container, keine Klasse auf dem\nSVG — UND reicht zusätzlich die Farbe durch, anders als `.ep-card-icon`.** Wie\n`.ep-card-icon` (siehe `icon-card.component.ts`, Entscheidung 2) setzt\ncss/components.css:1369 Maße und Stroke über den Nachfahren-Selektor\n`.ep-feature-icon svg{width:26px;height:26px;stroke-width:var(--icon-stroke-md)}`\n— NICHT über eine Klasse auf dem SVG selbst, anders als `.stoerer-icon`\n(`stoerer.component.ts`), das direkt auf dem projizierten `<svg>` sitzt. Der\nKonsument liefert deshalb ein unverändertes\n`<svg cdsIcon viewBox=\"…\" aria-hidden=\"true\" focusable=\"false\">…</svg>` OHNE\nzusätzliche Größenklasse, projiziert über `<ng-content select=\"[cdsIcon]\">`\n(Kontrakt in der Story „Interaktiv“ per `getBoundingClientRect()` gepinnt).\nAnders als `.ep-card-icon` (nur `background`, kein `color`) setzt\n`.ep-feature-icon` UND ihre vier `[data-area]`-Varianten (css/components.css:\n1367–1372) zusätzlich `color` auf dem Container selbst. Das Mockup nutzt das\naktiv aus: die projizierten SVGs dort (`docs/index.html:10406` u. a.) tragen\n`stroke=\"currentColor\"` statt eines hart codierten Bereichstons — die Farbe\nkommt allein aus dem Container, ein Icon-Autor muss den Bereich der Kachel gar\nnicht kennen. Bei `cds-icon-card` ist das nicht möglich (kein `color` am\nContainer), dort trägt jedes Icon seinen Bereichston selbst im `stroke`-Attribut.\nStory „Interaktiv“ demonstriert das mit `stroke=\"currentColor\"` und prüft die\ngeerbte Farbe per `getComputedStyle()`. Die Icon-Kachel selbst ist im Mockup\n(`docs/index.html:4883` u. a.) ein `<span aria-hidden=\"true\">`, kein `<div>` —\nrein dekorativ, das SVG trägt bereits sein eigenes `aria-hidden`; die Komponente\nübernimmt beides unverändert.\n\n**CTA: `<a>` mit `href`, sonst `<span>` — nie ein `<a>` ohne `href`.**\ncss/components.css:1376 zielt mit `.ep-feature-body>.card-cta-link` auf den\ndirekten Nachfahren; Angulars `@if`/`@else` fügt dafür kein Wrapper-Element ein\n(kompiliert zu Kommentar-Ankern, kein Element), der Kindselektor bleibt also in\nbeiden Zweigen erhalten. Ausgezählt in `docs/index.html`: alle 13 Vorkommen von\n`.card-cta-link` innerhalb von `.ep-feature-body` sind entweder ein `<a\nhref=\"…\">` (12×) oder ein `<span>` (1×, `docs/index.html:13216`,\n„Landingpage folgt“ — Platzhalter für ein Ziel, das noch fehlt). Ein `<a>` OHNE\n`href` kommt kein einziges Mal vor — dieselbe Falle, die `cds-icon-card` über\n`isLink()` schließt (ein Link-Tag ohne `href` ist weder fokussierbar noch hat\nes eine Link-Rolle, sieht mit der `.card-cta-link`-Optik aber trotzdem wie ein\nbedienbares Element aus). Hier entscheidet sich das nicht am Host-Tag (der ist\nbei `cdsFeature` immer `<div>`), sondern strukturell im eigenen Template:\n`ctaHref` gesetzt → `<a class=\"card-cta-link\" [href]=\"ctaHref()\">`, leer →\n`<span class=\"card-cta-link\">` mit identischer Optik und identischem Pfeil,\naber ohne Link-Rolle (siehe Story „Ohne Href“). Ein `[attr.href]`-Nullwert wäre\ndieselbe Krücke gewesen wie ein `<a>` ohne `href` zu tolerieren; der\n`@if`/`@else`-Zweig macht den Fall stattdessen unmöglich, statt ihn nur\nabzufedern. `data-area` sitzt auf beiden Zweigen, weil `.card-cta-link[data-area]`\n(css/components.css:1363–1366) ein direkter Attributselektor ist, keine\nNachfahren-Regel wie `.ep-card[data-area] .ep-card-cta` bei der Icon-Karte —\nohne eigenes `data-area` bliebe der Link/Span auf der CSS-Vorgabefarbe, auch bei\ngesetztem `area`.\n\n**`ctaAriaLabel` — der zugängliche Name ist im Mockup die Regel, nicht die\nAusnahme.** Erster Anlauf dieser Komponente hatte keinen solchen Input, mit der\n(falschen) Annahme, das Mockup verzichte meist auf einen zusätzlichen\n`aria-label`. Ausgezählt: von den 12 `<a class=\"card-cta-link\">` in\n`.ep-feature-body` tragen 11 einen `aria-label`, nur einer nicht\n(„Contentletter abonnieren“, `docs/index.html:15203` — der sichtbare Text ist\ndort bereits eindeutig). Grund: nur 3 verschiedene sichtbare Texte verteilen\nsich auf diese 12 Links, „Zur Landingpage“ allein zehnmal\n(`docs/index.html:11587` u. a.) — eine Screenreader-Linkliste hörte sonst\nzehnmal denselben Namen ohne Unterscheidung. Das Mockup disambiguiert\ndurchgängig mit der Form `<sichtbarer Text>: <Ziel>`\n(`aria-label=\"Zur Landingpage: KI Kickstart Workshops\"`). `ctaAriaLabel` ist\ndeshalb Beiwerk (Default `''`): leer bleibt der sichtbare `ctaLabel`-Text der\nzugängliche Name (passt für einen eindeutigen CTA-Text wie „Contentletter\nabonnieren“), gesetzt überschreibt er ihn — Konsumenten mit einer Liste\ngleichlautender CTA-Texte (wie den zehn „Zur Landingpage“ im Mockup) setzen ihn\npro Instanz auf `<ctaLabel>: <Ziel>`. Wirkt nur am `<a>`-Zweig: `aria-label` an\neinem `<span>` ohne Rolle hat keinen verlässlichen Effekt im\nAccessibility-Baum, und der `<span>`-Zweig ist ohnehin nicht interaktiv.\n\nVerwendungsguidance dieser Gruppe: siehe Card (`komponenten-cards-teaser-card--verwendung`).",
8
8
  "jsDocTags": {},
9
9
  "argTypes": {
10
10
  "area": {
@@ -4,7 +4,7 @@
4
4
  "id": "komponenten-cards-teaser-featured-karte",
5
5
  "name": "FeaturedCardComponent",
6
6
  "path": "./src/lib/featured-card/featured-card.stories.ts",
7
- "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.",
7
+ "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`).",
8
8
  "jsDocTags": {},
9
9
  "argTypes": {
10
10
  "area": {
@@ -4,7 +4,7 @@
4
4
  "id": "komponenten-cards-teaser-icon-karte",
5
5
  "name": "IconCardComponent",
6
6
  "path": "./src/lib/icon-card/icon-card.stories.ts",
7
- "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“).",
7
+ "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`).",
8
8
  "jsDocTags": {},
9
9
  "argTypes": {
10
10
  "area": {
@@ -4,7 +4,7 @@
4
4
  "id": "komponenten-cards-teaser-klickbare-karte",
5
5
  "name": "LinkCardComponent",
6
6
  "path": "./src/lib/link-card/link-card.stories.ts",
7
- "description": "LinkCard (cds-link-card) — Wrapper um `a.card.card-elevated` aus css/components.css\n(css/components.css:147–154) samt `.card-cta-link` (css/components.css:1347–1356).\n\nDie Lücke, die `CardComponent` bewusst offen lässt (siehe deren Klassendoku):\n`.card-elevated` wirkt im CSS ausschließlich auf `a.card-elevated` (Riegel aus\n`CONTRIBUTING.md` §4, „Elevation = Interaktivität“) — eine `<article>`/`<div>`-Karte\nbekommt den Schatten auch mit gesetzter Klasse nicht. Diese Komponente rendert\ndeshalb ein echtes `<a>`, die ganze Fläche ist der Link, kein Klick-Handler auf\neinem nicht-interaktiven Element.\n\nStruktur wie `CardComponent`: optionale bereichsgefärbte `.card-media` mit der\nechten Bereichs-Glyphe, `.card-body` mit `.card-eyebrow` / `.card-title` /\n`.card-text`. Neu ist der Fuß `.card-cta-link`, optional unten angeheftet\n(`--pinned`, für gleich hohe Karten im Raster über `.card-body{flex:1}`).\n\n**Entscheidung — Fuß ist ein `<span>`, kein zweites `<a>`.** Die ganze Karte ist\nbereits `a.card-elevated`; ein verschachteltes `<a>` im Fuß wäre ungültiges HTML\n(der Browser schließt es beim Parsen vorzeitig) und würde zwei Klickziele auf\nderselben Fläche erzeugen. `docs/index.html:4707–4716` zeigt exakt dieses Muster:\n`<span class=\"card-cta-link card-cta-link--pinned\">…</span>` innerhalb von\n`a.card.card-elevated`. `.card-cta-link` selbst ist nicht auf `a.` gescoped (anders\nals `.card-elevated`), der Span trägt Farbe und Typografie also unverändert.\n\n**Entscheidung — Pfeil-Suffix wird von der Komponente ergänzt, nicht Teil von\n`ctaLabel`.** Der Pfeil ist reine visuelle Affordanz, kein Inhalt — `docs/index.html`\n(Abschnitt „Klickbare Karte“) kapselt ihn deshalb selbst in ein\n`aria-hidden`-Span. Läge das Zeichen im `ctaLabel`-Text, müsste jeder Aufrufer\nselbst an das `aria-hidden` denken; das wird hier stattdessen einmalig in der\nKomponente erledigt.\n\n**Entscheidung — kein `data-area` auf `.card-cta-link`.** Die Doku hält die\nCTA-Farbe in gemischten Bereichs-Listen bewusst einheitlich Corporate-Petrol\n(`docs/index.html:8762`: „CTA-Farbe einheitlich `--co-700`“), unabhängig vom\n`area` der Karte. Der Fuß bleibt deshalb ohne `data-area`-Bindung.\n\n**Entscheidung — Host als Flex-Container.** Im Raster ist der Host das\nGrid-Kind, das `align-items:stretch` auf Zeilenhöhe zieht, nicht `a.card`. Ohne\neigene Regel blieb die Karte auf Inhaltshöhe, und eine Karte ohne Eyebrow endete\nsamt `--pinned`-Fuß höher als ihre Nachbarn. `:host{display:flex}` mit\n`flex:1` an der Karte gibt die gestreckte Höhe an `a.card` weiter; Story\n„Im Raster“ pinnt das.",
7
+ "description": "LinkCard (cds-link-card) — Wrapper um `a.card.card-elevated` aus css/components.css\n(css/components.css:147–154) samt `.card-cta-link` (css/components.css:1347–1356).\n\nDie Lücke, die `CardComponent` bewusst offen lässt (siehe deren Klassendoku):\n`.card-elevated` wirkt im CSS ausschließlich auf `a.card-elevated` (Riegel aus\n`CONTRIBUTING.md` §4, „Elevation = Interaktivität“) — eine `<article>`/`<div>`-Karte\nbekommt den Schatten auch mit gesetzter Klasse nicht. Diese Komponente rendert\ndeshalb ein echtes `<a>`, die ganze Fläche ist der Link, kein Klick-Handler auf\neinem nicht-interaktiven Element.\n\nStruktur wie `CardComponent`: optionale bereichsgefärbte `.card-media` mit der\nechten Bereichs-Glyphe, `.card-body` mit `.card-eyebrow` / `.card-title` /\n`.card-text`. Neu ist der Fuß `.card-cta-link`, optional unten angeheftet\n(`--pinned`, für gleich hohe Karten im Raster über `.card-body{flex:1}`).\n\n**Entscheidung — Fuß ist ein `<span>`, kein zweites `<a>`.** Die ganze Karte ist\nbereits `a.card-elevated`; ein verschachteltes `<a>` im Fuß wäre ungültiges HTML\n(der Browser schließt es beim Parsen vorzeitig) und würde zwei Klickziele auf\nderselben Fläche erzeugen. `docs/index.html:4707–4716` zeigt exakt dieses Muster:\n`<span class=\"card-cta-link card-cta-link--pinned\">…</span>` innerhalb von\n`a.card.card-elevated`. `.card-cta-link` selbst ist nicht auf `a.` gescoped (anders\nals `.card-elevated`), der Span trägt Farbe und Typografie also unverändert.\n\n**Entscheidung — Pfeil-Suffix wird von der Komponente ergänzt, nicht Teil von\n`ctaLabel`.** Der Pfeil ist reine visuelle Affordanz, kein Inhalt — `docs/index.html`\n(Abschnitt „Klickbare Karte“) kapselt ihn deshalb selbst in ein\n`aria-hidden`-Span. Läge das Zeichen im `ctaLabel`-Text, müsste jeder Aufrufer\nselbst an das `aria-hidden` denken; das wird hier stattdessen einmalig in der\nKomponente erledigt.\n\n**Entscheidung — kein `data-area` auf `.card-cta-link`.** Die Doku hält die\nCTA-Farbe in gemischten Bereichs-Listen bewusst einheitlich Corporate-Petrol\n(`docs/index.html:8762`: „CTA-Farbe einheitlich `--co-700`“), unabhängig vom\n`area` der Karte. Der Fuß bleibt deshalb ohne `data-area`-Bindung.\n\n**Entscheidung — Host als Flex-Container.** Im Raster ist der Host das\nGrid-Kind, das `align-items:stretch` auf Zeilenhöhe zieht, nicht `a.card`. Ohne\neigene Regel blieb die Karte auf Inhaltshöhe, und eine Karte ohne Eyebrow endete\nsamt `--pinned`-Fuß höher als ihre Nachbarn. `:host{display:flex}` mit\n`flex:1` an der Karte gibt die gestreckte Höhe an `a.card` weiter; Story\n„Im Raster“ pinnt das.\n\nVerwendungsguidance dieser Gruppe: siehe Card (`komponenten-cards-teaser-card--verwendung`).",
8
8
  "jsDocTags": {},
9
9
  "argTypes": {
10
10
  "area": {
@@ -4,7 +4,7 @@
4
4
  "id": "komponenten-cards-teaser-statcard",
5
5
  "name": "StatCardComponent",
6
6
  "path": "./src/lib/stat-card/stat-card.stories.ts",
7
- "description": "StatCard — Wrapper um `.card-stat` aus css/components.css → „Stat Card“.\n\nFlache, statische Kennzahlen-Karte mit bereichsgefärbtem Top-Akzent (data-area),\ngroßem Wert (.card-stat-value), Label (.card-stat-label) und optionalem\nTrend-Pill (.card-stat-trend.up/.down) inkl. Richtungs-Pfeil — wie docs/index.html.",
7
+ "description": "StatCard — Wrapper um `.card-stat` aus css/components.css → „Stat Card“.\n\nFlache, statische Kennzahlen-Karte mit bereichsgefärbtem Top-Akzent (data-area),\ngroßem Wert (.card-stat-value), Label (.card-stat-label) und optionalem\nTrend-Pill (.card-stat-trend.up/.down) inkl. Richtungs-Pfeil — wie docs/index.html.\n\nVerwendungsguidance dieser Gruppe: siehe Card (`komponenten-cards-teaser-card--verwendung`).",
8
8
  "jsDocTags": {},
9
9
  "argTypes": {
10
10
  "area": {