@conciso/design-system-mcp 2.5.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 (63) hide show
  1. package/package.json +1 -1
  2. package/snapshot/manifests/components.json +187 -27
  3. package/snapshot/manifests/docs.json +0 -128
  4. package/snapshot/services/addon-docs/mdx/komponenten-buchungsformular--/303/274bersicht.json +1 -1
  5. package/snapshot/services/addon-docs/mdx/komponenten-call-to-action-cta-band.json +18 -0
  6. package/snapshot/services/addon-docs/mdx/komponenten-cards-teaser-card.json +18 -0
  7. package/snapshot/services/addon-docs/mdx/komponenten-chips-badges-pills-chip.json +18 -0
  8. package/snapshot/services/addon-docs/mdx/komponenten-code-block-code-block.json +18 -0
  9. package/snapshot/services/addon-docs/mdx/komponenten-dropdowns-custom-select.json +18 -0
  10. package/snapshot/services/addon-docs/mdx/komponenten-feedback-snackbar.json +18 -0
  11. package/snapshot/services/addon-docs/mdx/komponenten-footer-komplett.json +18 -0
  12. package/snapshot/services/addon-docs/mdx/komponenten-hero-hero-bild.json +18 -0
  13. package/snapshot/services/addon-docs/mdx/komponenten-inputs-forms-textfeld.json +18 -0
  14. package/snapshot/services/addon-docs/mdx/komponenten-navigation-topnav.json +18 -0
  15. package/snapshot/services/addon-docs/mdx/komponenten-sektion-sektion.json +18 -0
  16. package/snapshot/services/addon-docs/mdx/komponenten-slider-carousel-carousel.json +18 -0
  17. package/snapshot/services/addon-docs/mdx/komponenten-tabelle-tabelle.json +18 -0
  18. package/snapshot/services/addon-docs/mdx/komponenten-theme-umschalter-cycle-button.json +18 -0
  19. package/snapshot/services/addon-docs/mdx/komponenten-zitate-testimonials-blockquote.json +18 -0
  20. package/snapshot/services/addon-docs/mdx/marke-logo-logo.json +18 -0
  21. package/snapshot/services/core/docgen/komponenten-call-to-action-downloadcta.json +1 -1
  22. package/snapshot/services/core/docgen/komponenten-cards-teaser-feature-liste.json +1 -1
  23. package/snapshot/services/core/docgen/komponenten-cards-teaser-featured-karte.json +1 -1
  24. package/snapshot/services/core/docgen/komponenten-cards-teaser-icon-karte.json +1 -1
  25. package/snapshot/services/core/docgen/komponenten-cards-teaser-klickbare-karte.json +1 -1
  26. package/snapshot/services/core/docgen/komponenten-cards-teaser-statcard.json +1 -1
  27. package/snapshot/services/core/docgen/komponenten-cards-teaser-statstrip.json +1 -1
  28. package/snapshot/services/core/docgen/komponenten-cards-teaser-tier-trenner.json +1 -1
  29. package/snapshot/services/core/docgen/komponenten-chips-badges-pills-bereichs-badge.json +1 -1
  30. package/snapshot/services/core/docgen/komponenten-chips-badges-pills-pill.json +1 -1
  31. package/snapshot/services/core/docgen/komponenten-chips-badges-pills-status-badge.json +1 -1
  32. package/snapshot/services/core/docgen/komponenten-dropdowns-combobox.json +1 -1
  33. package/snapshot/services/core/docgen/komponenten-footer-oberer-teil.json +1 -1
  34. package/snapshot/services/core/docgen/komponenten-footer-unterer-teil.json +1 -1
  35. package/snapshot/services/core/docgen/komponenten-hero-st/303/266rer.json +1 -1
  36. package/snapshot/services/core/docgen/komponenten-inputs-forms-auswahlfeld.json +1 -1
  37. package/snapshot/services/core/docgen/komponenten-inputs-forms-checkbox.json +1 -1
  38. package/snapshot/services/core/docgen/komponenten-inputs-forms-radio.json +1 -1
  39. package/snapshot/services/core/docgen/komponenten-inputs-forms-skala.json +1 -1
  40. package/snapshot/services/core/docgen/komponenten-inputs-forms-slider.json +1 -1
  41. package/snapshot/services/core/docgen/komponenten-inputs-forms-textbereich.json +1 -1
  42. package/snapshot/services/core/docgen/komponenten-slider-carousel-logocarousel.json +1 -1
  43. package/snapshot/services/core/docgen/komponenten-tabelle-vergleichstabelle.json +1 -1
  44. package/snapshot/services/core/docgen/komponenten-theme-umschalter-dropdown.json +1 -1
  45. package/snapshot/services/core/docgen/komponenten-theme-umschalter-segment.json +1 -1
  46. package/snapshot/services/core/docgen/komponenten-zitate-testimonials-teamvoice.json +1 -1
  47. package/snapshot/services/core/docgen/komponenten-zitate-testimonials-testimonial.json +1 -1
  48. package/snapshot/services/addon-docs/mdx/komponenten-call-to-action--verwendung.json +0 -18
  49. package/snapshot/services/addon-docs/mdx/komponenten-cards-teaser--verwendung.json +0 -18
  50. package/snapshot/services/addon-docs/mdx/komponenten-chips-badges-pills--verwendung.json +0 -18
  51. package/snapshot/services/addon-docs/mdx/komponenten-code-block--verwendung.json +0 -18
  52. package/snapshot/services/addon-docs/mdx/komponenten-dropdowns--verwendung.json +0 -18
  53. package/snapshot/services/addon-docs/mdx/komponenten-feedback--verwendung.json +0 -18
  54. package/snapshot/services/addon-docs/mdx/komponenten-footer--verwendung.json +0 -18
  55. package/snapshot/services/addon-docs/mdx/komponenten-hero--/303/274bersicht.json +0 -18
  56. package/snapshot/services/addon-docs/mdx/komponenten-inputs-forms--verwendung.json +0 -18
  57. package/snapshot/services/addon-docs/mdx/komponenten-navigation--verwendung.json +0 -18
  58. package/snapshot/services/addon-docs/mdx/komponenten-sektion--/303/274bersicht.json +0 -18
  59. package/snapshot/services/addon-docs/mdx/komponenten-slider-carousel--verwendung.json +0 -18
  60. package/snapshot/services/addon-docs/mdx/komponenten-tabelle--/303/274bersicht.json +0 -18
  61. package/snapshot/services/addon-docs/mdx/komponenten-theme-umschalter--verwendung.json +0 -18
  62. package/snapshot/services/addon-docs/mdx/komponenten-zitate-testimonials--verwendung.json +0 -18
  63. package/snapshot/services/addon-docs/mdx/marke-logo--verwendung.json +0 -18
@@ -17,14 +17,6 @@
17
17
  },
18
18
  "summary": "# Brand Areas Vier Unternehmensbereiche mit eigenem Farbsystem, alle konsistent und stimmi..."
19
19
  },
20
- "marke-logo--verwendung": {
21
- "id": "marke-logo--verwendung",
22
- "name": "Verwendung",
23
- "mdx": {
24
- "$ref": "../services/addon-docs/mdx/marke-logo--verwendung.json#/components/marke-logo--verwendung/docs/marke-logo--verwendung"
25
- },
26
- "summary": "# Verwendung Verwendungsregeln zum Logo: Größen, Schutzraum, Einsatz nach Hintergrund, Bar..."
27
- },
28
20
  "marke-bildsprache--übersicht": {
29
21
  "id": "marke-bildsprache--übersicht",
30
22
  "name": "Übersicht",
@@ -89,30 +81,6 @@
89
81
  },
90
82
  "summary": "# Barrierefreiheit Kontrastverhältnisse aller Brand-Farben, Tastaturnavigation, ARIA Patte..."
91
83
  },
92
- "komponenten-chips-badges-pills--verwendung": {
93
- "id": "komponenten-chips-badges-pills--verwendung",
94
- "name": "Verwendung",
95
- "mdx": {
96
- "$ref": "../services/addon-docs/mdx/komponenten-chips-badges-pills--verwendung.json#/components/komponenten-chips-badges-pills--verwendung/docs/komponenten-chips-badges-pills--verwendung"
97
- },
98
- "summary": "# Verwendung Drei Formen, die leicht verwechselt werden: interaktive Filter-Chips (Toggle)..."
99
- },
100
- "komponenten-inputs-forms--verwendung": {
101
- "id": "komponenten-inputs-forms--verwendung",
102
- "name": "Verwendung",
103
- "mdx": {
104
- "$ref": "../services/addon-docs/mdx/komponenten-inputs-forms--verwendung.json#/components/komponenten-inputs-forms--verwendung/docs/komponenten-inputs-forms--verwendung"
105
- },
106
- "summary": "# Verwendung Feldkomponenten (Text · E-Mail · Select · Textarea · Checkbox · Radio), berei..."
107
- },
108
- "komponenten-dropdowns--verwendung": {
109
- "id": "komponenten-dropdowns--verwendung",
110
- "name": "Verwendung",
111
- "mdx": {
112
- "$ref": "../services/addon-docs/mdx/komponenten-dropdowns--verwendung.json#/components/komponenten-dropdowns--verwendung/docs/komponenten-dropdowns--verwendung"
113
- },
114
- "summary": "# Verwendung Auswahl-Komponenten über das native `` hinaus: Custom Select (gestylte Einzel..."
115
- },
116
84
  "komponenten-buchungsformular--übersicht": {
117
85
  "id": "komponenten-buchungsformular--übersicht",
118
86
  "name": "Übersicht",
@@ -121,102 +89,6 @@
121
89
  },
122
90
  "summary": "# Buchungsformular Verbindliche Buchung eines konkreten Termins, am Beispiel eines Seminar..."
123
91
  },
124
- "komponenten-feedback--verwendung": {
125
- "id": "komponenten-feedback--verwendung",
126
- "name": "Verwendung",
127
- "mdx": {
128
- "$ref": "../services/addon-docs/mdx/komponenten-feedback--verwendung.json#/components/komponenten-feedback--verwendung/docs/komponenten-feedback--verwendung"
129
- },
130
- "summary": "# Verwendung Drei Snackbar-Varianten (Default, OK, Fehler) für Kontaktformular, Newsletter..."
131
- },
132
- "komponenten-cards-teaser--verwendung": {
133
- "id": "komponenten-cards-teaser--verwendung",
134
- "name": "Verwendung",
135
- "mdx": {
136
- "$ref": "../services/addon-docs/mdx/komponenten-cards-teaser--verwendung.json#/components/komponenten-cards-teaser--verwendung/docs/komponenten-cards-teaser--verwendung"
137
- },
138
- "summary": "# Verwendung Generische Karten (statisch, Link-Karte) und Inhaltskarten (Stat), alle Brand..."
139
- },
140
- "komponenten-call-to-action--verwendung": {
141
- "id": "komponenten-call-to-action--verwendung",
142
- "name": "Verwendung",
143
- "mdx": {
144
- "$ref": "../services/addon-docs/mdx/komponenten-call-to-action--verwendung.json#/components/komponenten-call-to-action--verwendung/docs/komponenten-call-to-action--verwendung"
145
- },
146
- "summary": "# Verwendung Call to Action deckt zwei Pattern-Familien ab: das Page-End-CTA-Band als letz..."
147
- },
148
- "komponenten-tabelle--übersicht": {
149
- "id": "komponenten-tabelle--übersicht",
150
- "name": "Übersicht",
151
- "mdx": {
152
- "$ref": "../services/addon-docs/mdx/komponenten-tabelle--übersicht.json#/components/komponenten-tabelle--übersicht/docs/komponenten-tabelle--übersicht"
153
- },
154
- "summary": "# Tabelle Barrierefreie Datentabellen mit ``, `scope`-Attributen und Tastaturnavigation, i..."
155
- },
156
- "komponenten-zitate-testimonials--verwendung": {
157
- "id": "komponenten-zitate-testimonials--verwendung",
158
- "name": "Verwendung",
159
- "mdx": {
160
- "$ref": "../services/addon-docs/mdx/komponenten-zitate-testimonials--verwendung.json#/components/komponenten-zitate-testimonials--verwendung/docs/komponenten-zitate-testimonials--verwendung"
161
- },
162
- "summary": "# Verwendung Zitate & Testimonials führt vier Formen: Blockquote, Testimonial-Card, Team-S..."
163
- },
164
- "komponenten-code-block--verwendung": {
165
- "id": "komponenten-code-block--verwendung",
166
- "name": "Verwendung",
167
- "mdx": {
168
- "$ref": "../services/addon-docs/mdx/komponenten-code-block--verwendung.json#/components/komponenten-code-block--verwendung/docs/komponenten-code-block--verwendung"
169
- },
170
- "summary": "# Verwendung Code-Block stellt Quellcode und Terminal-Ausgaben für Wissens- und Technikbei..."
171
- },
172
- "komponenten-slider-carousel--verwendung": {
173
- "id": "komponenten-slider-carousel--verwendung",
174
- "name": "Verwendung",
175
- "mdx": {
176
- "$ref": "../services/addon-docs/mdx/komponenten-slider-carousel--verwendung.json#/components/komponenten-slider-carousel--verwendung/docs/komponenten-slider-carousel--verwendung"
177
- },
178
- "summary": "# Verwendung Slider & Carousel führt zwei getrennte Formen: das Bild-Carousel zur Seiten-I..."
179
- },
180
- "komponenten-sektion--übersicht": {
181
- "id": "komponenten-sektion--übersicht",
182
- "name": "Übersicht",
183
- "mdx": {
184
- "$ref": "../services/addon-docs/mdx/komponenten-sektion--übersicht.json#/components/komponenten-sektion--übersicht/docs/komponenten-sektion--übersicht"
185
- },
186
- "summary": "# Sektion Das strukturelle Gerüst, in dem auf den Beispielseiten praktisch jeder andere Ba..."
187
- },
188
- "komponenten-navigation--verwendung": {
189
- "id": "komponenten-navigation--verwendung",
190
- "name": "Verwendung",
191
- "mdx": {
192
- "$ref": "../services/addon-docs/mdx/komponenten-navigation--verwendung.json#/components/komponenten-navigation--verwendung/docs/komponenten-navigation--verwendung"
193
- },
194
- "summary": "# Verwendung Navigation führt drei Elemente: Topnav, Breadcrumb und Back-to-Top-Button. Nu..."
195
- },
196
- "komponenten-hero--übersicht": {
197
- "id": "komponenten-hero--übersicht",
198
- "name": "Übersicht",
199
- "mdx": {
200
- "$ref": "../services/addon-docs/mdx/komponenten-hero--übersicht.json#/components/komponenten-hero--übersicht/docs/komponenten-hero--übersicht"
201
- },
202
- "summary": "# Hero Drei Hero-Varianten für den Seitenkopf: Hero-Image mit Caption-Overlay (21:9, Stand..."
203
- },
204
- "komponenten-footer--verwendung": {
205
- "id": "komponenten-footer--verwendung",
206
- "name": "Verwendung",
207
- "mdx": {
208
- "$ref": "../services/addon-docs/mdx/komponenten-footer--verwendung.json#/components/komponenten-footer--verwendung/docs/komponenten-footer--verwendung"
209
- },
210
- "summary": "# Verwendung Footer ist ein Zwei-Band-Layout: heller Main-Bereich mit Spalten-Grid (Brand ..."
211
- },
212
- "komponenten-theme-umschalter--verwendung": {
213
- "id": "komponenten-theme-umschalter--verwendung",
214
- "name": "Verwendung",
215
- "mdx": {
216
- "$ref": "../services/addon-docs/mdx/komponenten-theme-umschalter--verwendung.json#/components/komponenten-theme-umschalter--verwendung/docs/komponenten-theme-umschalter--verwendung"
217
- },
218
- "summary": "# Theme-Umschalter · Verwendung `docs/index.html` hat für den Theme-Umschalter eine eigene..."
219
- },
220
92
  "seitenmuster-wissensbeitrag--übersicht": {
221
93
  "id": "seitenmuster-wissensbeitrag--übersicht",
222
94
  "name": "Übersicht",
@@ -9,7 +9,7 @@
9
9
  "name": "Übersicht",
10
10
  "path": "./src/docs/komponenten/buchungsformular.mdx",
11
11
  "title": "Komponenten/Buchungsformular",
12
- "content": "import { Meta } from '@storybook/addon-docs/blocks';\n\n<Meta title=\"Komponenten/Buchungsformular\" name=\"Übersicht\" />\n\n# Buchungsformular\n\nVerbindliche Buchung eines konkreten Termins, am Beispiel eines Seminars\n(`Seitenmuster/Seminar · Training`). Komposition aus den Feldern von\n`Komponenten/Inputs & Forms/*`, ergänzt um drei Dinge, die es dort noch nicht gibt:\nGliederung in Fieldsets, bedingte Feldblöcke und einen Teilnehmenden-Repeater.\nKlassen-Präfix `.bk-*`, Logik in `main.js` über `data-bk`-Hooks.\n\n**Für dieses Formular gibt es kein eigenes Angular-Bauteil.** Ein Consumer setzt es\naus vorhandenen Wrapper-Komponenten zusammen: den Feldern aus\n`Komponenten/Inputs & Forms/*` (Textfeld, Auswahlfeld, Checkbox, Radio), den\nDropdowns aus `Komponenten/Dropdowns/*` und dem Button aus `Komponenten/Buttons/Button`.\nDie `.bk-*`-Klassen selbst kommen aus der CSS-Schicht und sind bereichsneutral, der\nBereichs-Akzent kommt ausschließlich über `data-accent` am umgebenden Container\n(siehe unten).\n\nQuelle: `docs/index.html#sec-booking` (Zeilen 4025 bis 4524).\n\n## Aufbau\n\nDie Reihenfolge folgt der Entscheidungslogik der Buchenden: erst, was gebucht wird,\ndann, wer bucht und zahlt, dann, wer teilnimmt, zuletzt die Rechtsfolge. Kaufmännisch\nheikle Felder (Rechnungsanschrift) stehen bewusst vor den Teilnehmenden, weil sie zur\nbuchenden Partei gehören und nicht zur Teilnehmerliste.\n\n| Gruppe | Untergruppe | Inhalt | Warum an dieser Stelle |\n|---|---|---|---|\n| Termin | — | Select mit den offenen Durchführungen (Pflicht) | Bestimmt Preis und Verfügbarkeit, also die Grundlage aller folgenden Felder |\n| Auftraggeber | Anmeldung als | Segmented Control „Firma“ / „Privatperson“ | Weiche vor vielen Feldern, steht in derselben Gruppe wie das, was sie schaltet |\n| Auftraggeber | Firmendaten | Firma (Pflicht), Abteilung, USt-IdNr., Bestellnummer oder Kostenstelle | Bedingt, nur bei „Firma“. Bestellnummer gehört hierher, weil sie auf die Rechnung muss |\n| Auftraggeber | Kontaktperson | Vorname, Nachname, E-Mail (alle Pflicht), Telefon | Empfängerin der Bestätigung, gehört zur buchenden Partei, nicht zwingend teilnehmend |\n| Rechnungsanschrift | — | Straße, PLZ, Ort (Pflicht), Land, Checkbox „andere Adresse“ | Anschrift der buchenden Partei, als Standardfall gesetzt, nicht als Sonderfall |\n| Rechnungsanschrift | Abweichende Rechnungsadresse | Empfänger, Anschrift (Pflicht), Rechnungs-E-Mail | Opt-in, eingeblendet von der Checkbox darüber, der Normalfall bleibt kurz |\n| Teilnehmende | — | „Ich nehme selbst teil“, Anzahl (Pflicht), Preiszeile, je Person Vorname, Nachname, E-Mail | Erst jetzt sinnvoll: die Anzahl entscheidet über Preis und Platzkontingent |\n| Abschluss | — | Anmerkungen, Teilnahmebedingungen (Pflicht), Datenschutz (Pflicht), Contentletter, Bestellübersicht, Submit | Rechtsfolge unmittelbar vor dem Button, nicht weiter oben versteckt |\n\n**Einseitig statt mehrstufig.** Das Formular ist lang, aber nicht komplex. Ein Stepper\nwürde die Gesamtlänge verbergen, das Zurückspringen erschweren und einen Zustand\neinführen, den es sonst nirgends im System gibt. Die Gliederung leisten die\nFieldsets: sichtbare Gruppen mit Legende, alle gleichzeitig scan- und korrigierbar.\n\n**Drei Typo-Stufen, sonst trägt die Gliederung nicht.** Gruppe `--ty-title-sm`\n(20 px, gemischt, `--tx-primary`), Untergruppe `--ty-name` (14 px halbfett, gemischt),\nFeldlabel 12 px Versalien in `--tx-secondary`. Zwei benachbarte Ebenen auf demselben\nToken laufen zu lassen ist der naheliegende Fehler: Die Gliederung ist dann\nsemantisch vorhanden und optisch unsichtbar, und ein Formular über 4.000 px lässt\nsich nicht mehr überfliegen.\n\n**Die Trennlinie gehört nur zwischen die Gruppen.** Sie sitzt über jedem\n`.bk-fieldset` der obersten Ebene, nie über einer `.bk-subgroup`. Der Grund ist\ninhaltlich, nicht dekorativ: Eine Linie zwischen „Anmeldung als“ und „Firmendaten“\nschneidet die Weiche von dem ab, was sie schaltet, und zerlegt eine Entscheidung in\nzwei scheinbar unabhängige Blöcke. Eine Gruppe ist deshalb eine Entscheidung samt\nihrer Folgen, kein Themenwort: „Auftraggeber“ umfasst die Weiche, die bedingten\nFirmendaten und die Kontaktperson, „Rechnungsanschrift“ umfasst die Adresse und den\nabweichenden Sonderfall.\n\n**Zwei Stellen für den Preis, mit unterschiedlicher Aufgabe.** Bei der Anzahl steht\ndie Preiszeile `.bk-summary` mit `role=\"status\"`, dort ändert sich der Wert und muss\nangesagt werden. Unmittelbar über dem Button steht die Bestellübersicht `.bk-order`\nmit Leistung, Termin, Auftraggeber, Plätzen und Gesamtbetrag. Der bloße Betrag würde\ndort nicht reichen: Was genau gebucht wird, steht sonst über tausend Pixel weiter\noben verstreut, und im Moment der verbindlichen Zusage ist nichts davon sichtbar.\n\n**Leere Zeilen der Übersicht bleiben stehen** und tragen `data-empty` („Noch nicht\ngewählt“, gedämpft). Eine Übersicht, die Zeilen ein- und ausblendet, springt beim\nAusfüllen, und man sieht nicht, was noch fehlt. Die Übersicht ist bewusst keine\nLive-Region: Sie wiederholt nur, was im Formular schon steht, und würde sonst jede\nÄnderung ein zweites Mal ansagen.\n\n**Nur Sternchen, kein „(optional)“.** Die Required-Marker trennen Pflicht- und\nKannfelder bereits, eine zweite Auszeichnung macht das Formular unnötig laut\n(dieselbe Konvention wie bei der Newsletter-Anmeldung in\n`Komponenten/Inputs & Forms/Verwendung`). Das freiwillige Contentletter-Häkchen ist\nam Schluss durch eine Haarlinie von den beiden Pflicht-Bestätigungen getrennt:\ngleiche Optik direkt darunter liest sich wie ein Dark Pattern, auch wenn keines\ngemeint ist.\n\n### Bereichs-Akzent\n\nEin Formular trägt genau eine Brand Area. Header, Eyebrow, Buttons und Segmented\nControl tragen dafür die passenden Bereichsklassen (z. B. `.btn-ki`, `.seg-ki`), und\nCheckboxen setzen `accent-color:var(--ki-ink)`. Die Links im Formular (Inhouse-\nAnfrage, Bedingungen, Datenschutz) tönt `data-accent=\"ki\"` am umgebenden Container\nmit: keine Inline-Farbe am einzelnen Link, sonst greift der Dark-Mode-Override nicht.\nDie `.bk-*`-Klassen selbst sind bereichsneutral, der Akzent kommt ausschließlich von\naußen.\n\n**Warum `--ki-ink` statt `--ki-500` für Checkboxen:** Die anderen Bereiche setzen\n`accent-color` auf ihr `-500`. Das Limette-Grün von KI erreicht auf Weiß aber nur\nrund 1,4:1 und wäre als angehakte Box kaum zu erkennen. `--ki-ink` ist der dafür\nvorgesehene modusbewusste Token (`--ki-800` im Light, `--ki-200` im Dark), dieselbe\nLogik wie bei `.btn-ki`. Zwei Corporate-Töne bleiben bewusst stehen: der Fokusring\nund der Feldrahmen im Fokus, weil Fokus Systemfeedback ist und keine Markenfläche.\n\n## Code-Rezept\n\nMinimales Markup: eine Gruppe mit Untergruppe, ein bedingter Block, der\nTeilnehmenden-Repeater, die Fehlerübersicht, die Bestellübersicht und eine\nPflicht-Checkbox mit verlinktem Rechtstext.\n\n```html\n<!-- Bereich am umgebenden Container, nicht am Formular: data-accent tönt .body-link\n und .t-co im Block auf die Bereichsfarbe. Die .bk-*-Klassen selbst sind\n bereichsneutral. -->\n<div data-accent=\"ki\">\n\n <!-- Konfiguration am Formular: Preis pro Platz, Höchstzahl. novalidate, damit die\n Inline-Meldungen statt der nativen Browser-Bubbles erscheinen. -->\n <form id=\"bk-demo\" data-booking data-price=\"1290\" data-max=\"12\" novalidate>\n\n <!-- Gruppe = eine Entscheidung samt ihrer Folgen. Untergruppen gliedern\n innerhalb, verschachtelte fieldsets geben Screenreadern die Zugehörigkeit\n mit. -->\n <fieldset class=\"bk-fieldset\">\n <legend class=\"bk-legend\">Auftraggeber</legend>\n\n <fieldset class=\"bk-subgroup\">\n <legend class=\"bk-sublegend\">Anmeldung als</legend>\n <div class=\"seg seg-ki\">…</div>\n </fieldset>\n\n <!-- Bedingter Block: hidden statt disabled, als Untergruppe IN der Gruppe\n seines Auslösers. data-required markiert Felder, die nur im\n eingeblendeten Zustand Pflicht sind; main.js setzt required mit. -->\n <fieldset class=\"bk-subgroup\" data-bk=\"firma-block\" hidden>\n <legend class=\"bk-sublegend\">Firmendaten</legend>\n <input type=\"text\" id=\"bk-demo-firma\" data-required\n autocomplete=\"organization\" data-err=\"Bitte gib den Firmennamen an\">\n </fieldset>\n\n <fieldset class=\"bk-subgroup\">\n <legend class=\"bk-sublegend\">Kontaktperson</legend>\n <div class=\"bk-grid-2\"><!-- kollabiert unter 768 px einspaltig -->\n <div class=\"field\">…</div>\n </div>\n </fieldset>\n </fieldset>\n\n <!-- Repeater: Container + template. main.js klont pro Platz einen Block, vergibt\n id/for und autocomplete-Section nach Position (section-tn1, section-tn2, …). -->\n <input type=\"number\" data-bk=\"anzahl\" value=\"1\" min=\"1\" max=\"12\" required>\n <div class=\"bk-summary\" role=\"status\">…</div>\n <div data-bk=\"personen\"></div>\n <template data-bk=\"person-template\">\n <fieldset class=\"bk-person\"><!-- Gruppe, nicht nur Optik -->\n <legend class=\"bk-person-title\" data-bk=\"person-title\">Teilnehmende 1</legend>\n <input type=\"text\" data-bk=\"p-vorname\" data-ac=\"given-name\" required>\n </fieldset>\n </template>\n\n <!-- Fehlerübersicht am Formularkopf: bekommt nach gescheitertem Submit den\n Fokus. -->\n <div class=\"bk-errors\" data-bk=\"errors\" role=\"alert\" tabindex=\"-1\" hidden>\n <ul data-bk=\"error-list\"></ul>\n </div>\n\n <!-- Bestellübersicht unmittelbar vor dem Button. Leere Zeilen bleiben stehen\n und tragen data-empty, statt zu verschwinden. -->\n <div class=\"bk-order\">\n <dl class=\"bk-order-list\">\n <div><dt>Termin</dt><dd data-bk=\"order-termin\" data-empty>Noch nicht gewählt</dd></div>\n </dl>\n <div class=\"bk-order-total\">…</div>\n </div>\n\n <!-- Pflicht-Checkbox: data-bk=\"consent-required\" + eigene Fehlermeldung. Der\n verlinkte Hinweis ist ein echter Anker, kein span: sonst ist er nicht per\n Tab erreichbar und wird nicht als Link angesagt. -->\n <div class=\"bk-consent\">\n <input type=\"checkbox\" id=\"bk-demo-agb\" data-bk=\"consent-required\"\n data-err=\"Bitte akzeptiere die Teilnahme- und Stornobedingungen\">\n <label for=\"bk-demo-agb\">Ich akzeptiere die\n <a class=\"body-link\" href=\"/teilnahmebedingungen\">Teilnahme- und Stornobedingungen</a>.\n </label>\n </div>\n </form>\n</div>\n```\n\n## Bedingte Felder\n\nZwei Weichen blenden Feldblöcke ein und aus. Beide folgen derselben Mechanik: das\n`hidden`-Attribut am Block, `data-required` an den Feldern, die nur im sichtbaren\nZustand Pflicht sind.\n\n| Auslöser | Eingeblendet | Verhalten |\n|---|---|---|\n| Segmented Control „Firma“ | Fieldset Firmendaten | Firma wird Pflichtfeld. Bei „Privatperson“ verschwindet der Block samt Fehlerzustand |\n| Checkbox „Rechnung geht an eine andere Adresse“ | Fieldset Abweichende Rechnungsadresse | Empfänger, Straße, PLZ und Ort werden Pflichtfelder |\n\n| Regel | Begründung |\n|---|---|\n| `hidden` statt `disabled` | Deaktivierte Felder werden beim Submit nicht mitgesendet und von Screenreadern übersprungen. Ausgeblendete Felder behalten ihren Wert und kommen beim Wiedereinblenden unverändert zurück |\n| `required` mitschalten | Ein unsichtbares Pflichtfeld blockiert die Absendung ohne sichtbare Ursache. `data-required` ist die Merkmarkierung, `required` wird beim Einblenden gesetzt und beim Ausblenden entfernt |\n| Fehlerzustand beim Ausblenden aufräumen | Sonst bleibt eine `.error-msg` im DOM stehen und wird beim nächsten Einblenden erneut angesagt |\n| Block liegt in der Gruppe seines Auslösers | Ein bedingter Block ist keine eigene Gruppe, sondern eine `.bk-subgroup` innerhalb der Gruppe, die ihn schaltet. Als Geschwister-Gruppe mit eigener Trennlinie wirkt er unabhängig, obwohl er ohne seinen Auslöser gar nicht existiert |\n| Block bleibt an seiner Position | Der eingeblendete Block erscheint unter seinem Auslöser, nicht am Formularanfang. Kein Layout-Sprung, kein Suchen |\n| Fokus wandert nicht automatisch | Ein erzwungener Fokussprung nach dem Klick auf eine Checkbox reißt Tastatur- und Screenreader-Nutzende aus dem Kontext. Der nächste Tab landet ohnehin im neuen Block |\n\n## Teilnehmende\n\n**Warum die Anzahl ein eigenes Feld ist.** Ein reiner „Weitere Person\nhinzufügen“-Repeater leitet die Anzahl aus der Listenlänge ab. Für eine Buchung\nreicht das nicht, weil die Zahl kaufmännisch trägt: Sie bestimmt den Preis und muss\ngegen das Platzkontingent des Termins validiert werden (`min`/`max`). Deshalb ist die\nAnzahl das führende Feld, die Namensblöcke folgen ihr, und der Hinzufügen-Button\nerhöht sie, statt sie zu umgehen. So gibt es genau eine Quelle der Wahrheit.\n\n| Interaktion | Verhalten |\n|---|---|\n| Anzahl erhöhen | Es werden Blöcke am Ende ergänzt. Bereits eingegebene Namen bleiben unberührt |\n| Anzahl verringern | Nur leere Blöcke am Ende fallen weg. Enthält einer noch Daten, bleibt die Anzahl stehen und eine `.error-msg` nennt den betroffenen Block |\n| „Weitere Person hinzufügen“ | Erhöht die Anzahl um eins und setzt den Fokus in das erste Feld des neuen Blocks |\n| „Entfernen“ | Löscht genau diesen Block, nummeriert die übrigen neu, senkt die Anzahl und setzt den Fokus auf das Anzahl-Feld. Ab zwei Blöcken sichtbar |\n| „Ich nehme selbst teil“ | Block 1 übernimmt Vorname, Nachname und E-Mail aus dem Kontakt und folgt weiteren Änderungen, bis der Block von Hand bearbeitet wird. Die Felder bleiben editierbar. Solange der Haken sitzt, hat Block 1 keinen Entfernen-Button |\n| Preiszeile | `.bk-summary` mit `role=\"status\"`. Zeigt Plätze, Einzelpreis und Summe, aktualisiert bei jeder Änderung der Anzahl |\n| Blockgrenze | Jeder Block ist ein `<fieldset class=\"bk-person\">` mit `<legend>`. Ohne diese Gruppe hört ein Screenreader dreimal nur „Vorname“, ohne zu wissen, zu wem |\n\n**Warum keine Auto-Nummerierung per CSS-Counter.** Die Blocknummer steckt nicht nur\nin der Überschrift, sondern auch in `id`, `for` und der `autocomplete`-Section. Beim\nEntfernen eines mittleren Blocks werden alle drei über `renumber()` neu vergeben,\nwährend die Feldwerte mit ihrem DOM-Knoten mitwandern. Es wird nichts umkopiert,\ndeshalb kann dabei auch nichts verloren gehen.\n\n## Validierung & Barrierefreiheit\n\nDas Formular nutzt dasselbe Muster wie die Kontaktformulare in\n`Komponenten/Inputs & Forms/Verwendung`: `novalidate` unterdrückt die nativen\nBubbles, geprüft wird beim Submit, Fehler erscheinen als `.field.has-error` plus\n`.error-msg` mit `role=\"alert\"` unter dem Feld, der Fokus springt auf das erste\nungültige Feld. Die Helfer `setError()` und `clearError()` in `main.js` sind für\nKontakt- und Buchungsformular dieselben.\n\n**Feldspezifische Meldungen.** Jedes Pflichtfeld trägt sein `data-err` im Markup\n(„Bitte gib den Ort an“ statt „Dieses Feld ist erforderlich“). Bei einem Formular\ndieser Länge ist eine generische Meldung wertlos, sobald mehrere Fehler gleichzeitig\nstehen. Ausgeblendete Blöcke werden übersprungen, geprüft wird nur, was sichtbar\nist.\n\n**Fehlerübersicht statt Alert-Salve.** Das kurze Kontaktformular gibt jeder Meldung\nein `role=\"alert\"`, bei drei Feldern funktioniert das. Hier entstehen bei einem\nleeren Submit über zehn Meldungen gleichzeitig, und gleichzeitig eingefügte Alerts\nergeben beim Screenreader eine unbrauchbare Ansage. Deshalb sammelt `.bk-errors` am\nFormularkopf alle Fehler als Sprungliste, trägt selbst das `role=\"alert\"` und\nbekommt den Fokus. Die Meldungen am Feld bleiben über `aria-describedby` verbunden\nund werden beim Fokussieren gelesen, tragen aber kein eigenes `role=\"alert\"` mehr\n(Parameter `quiet` an `setError()`).\n\n**Nachvalidierung.** Erst geprüft wird beim Absenden, nicht beim Tippen, damit\nniemand beim Ausfüllen angemeckert wird. Nach einem gescheiterten Submit kippt das\nFormular in den Nachvalidierungs-Modus: Jede Korrektur räumt die Meldung am Feld und\nden zugehörigen Eintrag in der Übersicht sofort weg.\n\n| Aspekt | Regel |\n|---|---|\n| Gruppierung | Jeder Abschnitt und jeder Teilnehmendenblock ist ein `<fieldset>` mit `<legend>`. Screenreader sagen die Gruppe vor dem Feld an, dadurch bleibt „Vorname“ in Block 3 unterscheidbar von „Vorname“ im Kontakt |\n| Pflicht-Checkboxen | `aria-required=\"true\"` an der Box selbst. Das sichtbare `*` im Label ist `aria-hidden`, ohne das Attribut wäre die Pflicht rein optisch |\n| Verlinkte Bestätigungen | Teilnahmebedingungen und Datenschutzhinweise sind echte `<a class=\"body-link\">` im Label, kein `<span>` |\n| Fehlerübersicht | `.bk-errors` mit `role=\"alert\"` und `tabindex=\"-1\"`, wird nach einem gescheiterten Submit fokussiert. Die Einträge sind Sprunglinks auf das jeweilige Feld |\n| Autofill | Durchgängige `autocomplete`-Tokens. Die abweichende Rechnungsadresse trägt `section-rechnung`, jeder Teilnehmendenblock `section-tn1`, `section-tn2` und so weiter, damit der Browser die Blöcke nicht gegenseitig überschreibt (WCAG 1.3.5) |\n| Anzahl und Preis | `.bk-summary` trägt `role=\"status\"`. Jede Änderung von Anzahl, Termin oder Personenliste wird dadurch angesagt, ohne den Fokus zu stören |\n| Fokusführung | Hinzufügen setzt den Fokus in den neuen Block, Entfernen zurück auf das Anzahl-Feld |\n| Entfernen-Button | `aria-label=\"Teilnehmende 3 entfernen\"`, weil „Entfernen“ allein in einer Liste gleichlautender Buttons mehrdeutig ist |\n| Pflichtfelder | Sichtbares `*` (`aria-hidden`) plus `aria-required=\"true\"`. Der Hinweis „Mit * markierte Felder sind Pflichtfelder“ steht vor dem ersten Feld |\n| Bestellübersicht | Bewusst ohne `role=\"status\"`, sie fasst nur zusammen, was im Formular schon steht. Semantisch eine `<dl>`, damit Bezeichnung und Wert paarweise gelesen werden |\n| Erfolgszustand | `.bk-success` mit `role=\"status\"` und `tabindex=\"-1\"`, wird nach dem Absenden fokussiert. Das Formular geht auf `hidden` |\n| Touch & Tastatur | Alle Felder und Buttons ab 44 px Höhe, native Radios und Checkboxen mit `accent-color`, Fokusring über `--focus-ring` |\n\n## Anfrage oder Direktbuchung\n\nIm System gibt es zwei Wege, wie aus Interesse ein Termin wird. Sie schließen sich\nnicht aus, sie gehören zu unterschiedlichen Angeboten. Die Entscheidung fällt am\nAngebot, nicht am Formular.\n\n| Kriterium | Anfrage (adaptive Kontaktseite) | Direktbuchung (dieses Formular) |\n|---|---|---|\n| Preis | individuell, wird im Gespräch bestimmt | fester Listenpreis pro Platz, sofort berechenbar |\n| Verfügbarkeit | offen, Termin entsteht erst | feste Durchführungen mit Platzkontingent |\n| Rechtsfolge | unverbindlich | verbindlich, Submit-Label benennt die Zahlungspflicht |\n| Datenbedarf | Name, E-Mail, Anliegen | zusätzlich Rechnungsanschrift und Teilnehmendenliste |\n| Typische Fälle | Inhouse-Training, Beratung, Workshop nach Zuschnitt | offenes Seminar mit veröffentlichter Terminliste |\n\nDie Beispielseite Seminar (`Seitenmuster/Seminar · Training`) bleibt bewusst beim\nAnfrage-Flow: Die Termin-Zeilen führen über `data-k-bereich` und `data-k-anliegen`\nauf die Kontaktseite. Das Buchungsformular ist die Variante für Angebote, bei denen\nPreis und Kontingent feststehen.\n\n## Für die Umsetzung\n\nDie Referenz-Demo in der Doku-Site ist vollständig bedienbar, aber ein Mockup ohne\nBackend. Wer die Komponente in die echte Site überführt, ob Mensch oder Assistent,\nbraucht die folgenden Punkte als Anforderungen, nicht als Ideen.\n\n**Wichtigster Punkt: Schutz gegen Datenverlust.** Ein ausgefülltes Formular enthält\nbis zu zwölf Teilnehmende mit je drei Feldern, dazu Kontakt und zwei Anschriften. Ein\nversehentliches Zurück, ein geschlossener Tab oder ein abgestürzter Browser wirft\nalles weg. Das Referenz-Markup hat keinen Zwischenspeicher; in Produktion ist er\nPflicht, nicht optional.\n\n| Anforderung | Umsetzung |\n|---|---|\n| Entwurf laufend sichern | Bei jedem `input` und `change` den Formularstand entprellt (ca. 500 ms) unter einem stabilen Schlüssel ablegen, zum Beispiel `booking-draft:<formular-id>` |\n| Wo ablegen | `sessionStorage` für den laufenden Tab. `localStorage` nur, wenn der Entwurf einen Browserneustart überleben soll, dann mit Ablaufdatum. Keine Zahlungs- oder Ausweisdaten, und Namen sowie Anschriften sind personenbezogen: Aufbewahrungsdauer und Rechtsgrundlage vorher mit dem internen Datenschutzbeauftragten klären (Michael Didion / Harry Ursinus, datenschutz@conciso.de) |\n| Wiederherstellen mit Ansage | Beim Laden nicht still befüllen. Einen Hinweis über dem Formular zeigen, mit der Möglichkeit, den Entwurf zu verwerfen |\n| Entwurf verwerfen | Nach erfolgreichem Submit den Schlüssel löschen, sonst taucht die alte Buchung bei der nächsten wieder auf |\n| Warnung beim Verlassen | `beforeunload` nur registrieren, solange es ungespeicherte Änderungen gibt, und nach dem Submit wieder abmelden |\n| Reihenfolge beim Wiederherstellen | Erst Kundentyp und die Checkbox für die abweichende Rechnungsadresse setzen, dann die bedingten Blöcke einblenden, dann die Anzahl setzen (das erzeugt die Teilnehmendenblöcke), erst danach die Werte der Personen eintragen |\n| Nicht mit „Ich nehme selbst teil“ kollidieren | Die Übernahme aus den Kontaktdaten läuft nur, solange ein Feld nicht von Hand bearbeitet wurde. Beim Wiederherstellen deshalb auch dieses Merkmal mitsichern |\n\n| Thema | Anforderung |\n|---|---|\n| Serverseitige Validierung | Die Prüfungen im Browser sind Komfort, keine Absicherung. Jede Regel muss serverseitig gespiegelt werden, inklusive der bedingten Pflichtfelder |\n| Platzkontingent | `data-max` ist im Referenz-Markup fest. Real kommt die Obergrenze pro Termin aus dem Backend und muss beim Submit erneut geprüft werden. Ausverkaufte Termine gehören nicht in den Select |\n| Doppelte Buchungen | Submit-Button nach dem ersten Klick sperren und die Anfrage mit einem Idempotenzschlüssel versehen |\n| Preis | `data-price` ist Anzeige, nie Berechnungsgrundlage. Die Summe entsteht serverseitig aus dem Termin |\n| Bestellübersicht | Welche Angaben bei einer zahlungspflichtigen Buchung zwingend unmittelbar vor dem Absenden stehen müssen, vor dem Livegang rechtlich prüfen lassen |\n| Ziele der Rechtstexte | Real zeigen die Consent-Links auf die veröffentlichten Seiten und öffnen sie so, dass das ausgefüllte Formular nicht verloren geht (`target=\"_blank\"` mit `rel=\"noopener\"` oder ein Overlay) |\n| Bereichsfarbe | `data-accent=\"ki|es|wo\"` sitzt am Container um das Formular, nicht am Formular selbst und nicht an den einzelnen Elementen |\n| Spam | Kein sichtbares Captcha als erste Wahl. Zeitmessung und ein verstecktes Honeypot-Feld reichen für ein Formular dieser Länge meist aus und kosten keine Barrierefreiheit |\n\n## Verwendung\n\n<div className=\"doc-eyebrow\">Dos & Don'ts</div>\n\n**✓ Tun**\n\n- Anzahl und Namensblöcke synchron halten, mit der Anzahl als einziger Quelle der Wahrheit\n- Bedingte Blöcke ein- und ausblenden statt Felder zu deaktivieren, und `required` mitschalten\n- Die Rechnungsanschrift des Anmelders als Standard setzen, die Abweichung als Opt-in anbieten\n- Das Submit-Label die Rechtsfolge benennen lassen, hier „Zahlungspflichtig buchen“\n- Jedem Pflichtfeld eine eigene Fehlermeldung über `data-err` geben\n- Ab etwa fünf Pflichtfeldern eine Fehlerübersicht zeigen und sie fokussieren, statt nur auf das erste Feld zu springen\n- Fehler beim Korrigieren sofort wieder wegräumen, am Feld und in der Übersicht\n- Gruppenüberschrift und Feldlabel typografisch klar trennen, sonst ist die Gliederung nur semantisch da\n- Eine Gruppe als eine Entscheidung samt ihrer Folgen behandeln, nicht als Themenwort\n- Vor dem zahlungspflichtigen Button zusammenfassen, was gebucht wird, nicht nur den Betrag\n- Den Bereich einmal über `data-accent` am umgebenden Container setzen, dann tönen auch die Links im Formular mit\n- Verlinkte Rechtstexte in den Bestätigungen als echten `<a>` auszeichnen\n\n**✕ Nicht tun**\n\n- Die Anzahl reduzieren und dabei ausgefüllte Namensblöcke still verwerfen\n- Teilnehmendenblock 1 nach „Ich nehme selbst teil“ auf `disabled` setzen, die Werte gingen beim Submit verloren\n- Firmen- und Rechnungsfelder auch Privatpersonen zeigen, nur mit „(optional)“ entschärft\n- Unverbindliche Anfrage und zahlungspflichtige Buchung in einem Formular mischen\n- Das Formular in einen Stepper zerlegen, nur um es kürzer wirken zu lassen\n- Ein Dutzend Meldungen mit `role=\"alert\"` gleichzeitig einfügen, das ergibt eine unverständliche Ansage\n- Die Teilnehmendenblöcke nur optisch durch eine Überschrift trennen, ohne `<fieldset>`\n- Pflichtsternchen und „(optional)“ gleichzeitig setzen, eine der beiden Auszeichnungen reicht\n- Ein freiwilliges Marketing-Häkchen optisch gleichrangig unter die Pflicht-Bestätigungen setzen\n- Eine Trennlinie zwischen eine Weiche und die Felder legen, die sie schaltet\n- Einzelnen Links im Formular eine Inline-Bereichsfarbe geben, das bricht im Dark Mode\n- Den verlinkten Rechtstext als `<span>` mit `cursor:pointer` bauen, er ist dann für Tastatur und Screenreader kein Link\n\n## Verwandte Seiten\n\n- `Komponenten/Inputs & Forms/*` (Textfeld, Auswahlfeld, Checkbox, Radio)\n- `Komponenten/Dropdowns/*`\n- `Komponenten/Buttons/Button`\n- Doku-Sektion: `docs/index.html#sec-booking`\n",
12
+ "content": "import { Meta } from '@storybook/addon-docs/blocks';\n\n<Meta title=\"Komponenten/Buchungsformular\" name=\"Übersicht\" />\n\n# Buchungsformular\n\nVerbindliche Buchung eines konkreten Termins, am Beispiel eines Seminars\n(`Seitenmuster/Seminar · Training`). Komposition aus den Feldern von\n`Komponenten/Inputs & Forms/*`, ergänzt um drei Dinge, die es dort noch nicht gibt:\nGliederung in Fieldsets, bedingte Feldblöcke und einen Teilnehmenden-Repeater.\nKlassen-Präfix `.bk-*`, Logik in `main.js` über `data-bk`-Hooks.\n\n**Für dieses Formular gibt es kein eigenes Angular-Bauteil.** Ein Consumer setzt es\naus vorhandenen Wrapper-Komponenten zusammen: den Feldern aus\n`Komponenten/Inputs & Forms/*` (Textfeld, Auswahlfeld, Checkbox, Radio), den\nDropdowns aus `Komponenten/Dropdowns/*` und dem Button aus `Komponenten/Buttons/Button`.\nDie `.bk-*`-Klassen selbst kommen aus der CSS-Schicht und sind bereichsneutral, der\nBereichs-Akzent kommt ausschließlich über `data-accent` am umgebenden Container\n(siehe unten).\n\nQuelle: `docs/index.html#sec-booking` (Zeilen 4025 bis 4524).\n\n## Aufbau\n\nDie Reihenfolge folgt der Entscheidungslogik der Buchenden: erst, was gebucht wird,\ndann, wer bucht und zahlt, dann, wer teilnimmt, zuletzt die Rechtsfolge. Kaufmännisch\nheikle Felder (Rechnungsanschrift) stehen bewusst vor den Teilnehmenden, weil sie zur\nbuchenden Partei gehören und nicht zur Teilnehmerliste.\n\n| Gruppe | Untergruppe | Inhalt | Warum an dieser Stelle |\n|---|---|---|---|\n| Termin | — | Select mit den offenen Durchführungen (Pflicht) | Bestimmt Preis und Verfügbarkeit, also die Grundlage aller folgenden Felder |\n| Auftraggeber | Anmeldung als | Segmented Control „Firma“ / „Privatperson“ | Weiche vor vielen Feldern, steht in derselben Gruppe wie das, was sie schaltet |\n| Auftraggeber | Firmendaten | Firma (Pflicht), Abteilung, USt-IdNr., Bestellnummer oder Kostenstelle | Bedingt, nur bei „Firma“. Bestellnummer gehört hierher, weil sie auf die Rechnung muss |\n| Auftraggeber | Kontaktperson | Vorname, Nachname, E-Mail (alle Pflicht), Telefon | Empfängerin der Bestätigung, gehört zur buchenden Partei, nicht zwingend teilnehmend |\n| Rechnungsanschrift | — | Straße, PLZ, Ort (Pflicht), Land, Checkbox „andere Adresse“ | Anschrift der buchenden Partei, als Standardfall gesetzt, nicht als Sonderfall |\n| Rechnungsanschrift | Abweichende Rechnungsadresse | Empfänger, Anschrift (Pflicht), Rechnungs-E-Mail | Opt-in, eingeblendet von der Checkbox darüber, der Normalfall bleibt kurz |\n| Teilnehmende | — | „Ich nehme selbst teil“, Anzahl (Pflicht), Preiszeile, je Person Vorname, Nachname, E-Mail | Erst jetzt sinnvoll: die Anzahl entscheidet über Preis und Platzkontingent |\n| Abschluss | — | Anmerkungen, Teilnahmebedingungen (Pflicht), Datenschutz (Pflicht), Contentletter, Bestellübersicht, Submit | Rechtsfolge unmittelbar vor dem Button, nicht weiter oben versteckt |\n\n**Einseitig statt mehrstufig.** Das Formular ist lang, aber nicht komplex. Ein Stepper\nwürde die Gesamtlänge verbergen, das Zurückspringen erschweren und einen Zustand\neinführen, den es sonst nirgends im System gibt. Die Gliederung leisten die\nFieldsets: sichtbare Gruppen mit Legende, alle gleichzeitig scan- und korrigierbar.\n\n**Drei Typo-Stufen, sonst trägt die Gliederung nicht.** Gruppe `--ty-title-sm`\n(20 px, gemischt, `--tx-primary`), Untergruppe `--ty-name` (14 px halbfett, gemischt),\nFeldlabel 12 px Versalien in `--tx-secondary`. Zwei benachbarte Ebenen auf demselben\nToken laufen zu lassen ist der naheliegende Fehler: Die Gliederung ist dann\nsemantisch vorhanden und optisch unsichtbar, und ein Formular über 4.000 px lässt\nsich nicht mehr überfliegen.\n\n**Die Trennlinie gehört nur zwischen die Gruppen.** Sie sitzt über jedem\n`.bk-fieldset` der obersten Ebene, nie über einer `.bk-subgroup`. Der Grund ist\ninhaltlich, nicht dekorativ: Eine Linie zwischen „Anmeldung als“ und „Firmendaten“\nschneidet die Weiche von dem ab, was sie schaltet, und zerlegt eine Entscheidung in\nzwei scheinbar unabhängige Blöcke. Eine Gruppe ist deshalb eine Entscheidung samt\nihrer Folgen, kein Themenwort: „Auftraggeber“ umfasst die Weiche, die bedingten\nFirmendaten und die Kontaktperson, „Rechnungsanschrift“ umfasst die Adresse und den\nabweichenden Sonderfall.\n\n**Zwei Stellen für den Preis, mit unterschiedlicher Aufgabe.** Bei der Anzahl steht\ndie Preiszeile `.bk-summary` mit `role=\"status\"`, dort ändert sich der Wert und muss\nangesagt werden. Unmittelbar über dem Button steht die Bestellübersicht `.bk-order`\nmit Leistung, Termin, Auftraggeber, Plätzen und Gesamtbetrag. Der bloße Betrag würde\ndort nicht reichen: Was genau gebucht wird, steht sonst über tausend Pixel weiter\noben verstreut, und im Moment der verbindlichen Zusage ist nichts davon sichtbar.\n\n**Leere Zeilen der Übersicht bleiben stehen** und tragen `data-empty` („Noch nicht\ngewählt“, gedämpft). Eine Übersicht, die Zeilen ein- und ausblendet, springt beim\nAusfüllen, und man sieht nicht, was noch fehlt. Die Übersicht ist bewusst keine\nLive-Region: Sie wiederholt nur, was im Formular schon steht, und würde sonst jede\nÄnderung ein zweites Mal ansagen.\n\n**Nur Sternchen, kein „(optional)“.** Die Required-Marker trennen Pflicht- und\nKannfelder bereits, eine zweite Auszeichnung macht das Formular unnötig laut\n(dieselbe Konvention wie bei der Newsletter-Anmeldung in\n`Komponenten/Inputs & Forms/Textfeld/Verwendung`). Das freiwillige Contentletter-Häkchen ist\nam Schluss durch eine Haarlinie von den beiden Pflicht-Bestätigungen getrennt:\ngleiche Optik direkt darunter liest sich wie ein Dark Pattern, auch wenn keines\ngemeint ist.\n\n### Bereichs-Akzent\n\nEin Formular trägt genau eine Brand Area. Header, Eyebrow, Buttons und Segmented\nControl tragen dafür die passenden Bereichsklassen (z. B. `.btn-ki`, `.seg-ki`), und\nCheckboxen setzen `accent-color:var(--ki-ink)`. Die Links im Formular (Inhouse-\nAnfrage, Bedingungen, Datenschutz) tönt `data-accent=\"ki\"` am umgebenden Container\nmit: keine Inline-Farbe am einzelnen Link, sonst greift der Dark-Mode-Override nicht.\nDie `.bk-*`-Klassen selbst sind bereichsneutral, der Akzent kommt ausschließlich von\naußen.\n\n**Warum `--ki-ink` statt `--ki-500` für Checkboxen:** Die anderen Bereiche setzen\n`accent-color` auf ihr `-500`. Das Limette-Grün von KI erreicht auf Weiß aber nur\nrund 1,4:1 und wäre als angehakte Box kaum zu erkennen. `--ki-ink` ist der dafür\nvorgesehene modusbewusste Token (`--ki-800` im Light, `--ki-200` im Dark), dieselbe\nLogik wie bei `.btn-ki`. Zwei Corporate-Töne bleiben bewusst stehen: der Fokusring\nund der Feldrahmen im Fokus, weil Fokus Systemfeedback ist und keine Markenfläche.\n\n## Code-Rezept\n\nMinimales Markup: eine Gruppe mit Untergruppe, ein bedingter Block, der\nTeilnehmenden-Repeater, die Fehlerübersicht, die Bestellübersicht und eine\nPflicht-Checkbox mit verlinktem Rechtstext.\n\n```html\n<!-- Bereich am umgebenden Container, nicht am Formular: data-accent tönt .body-link\n und .t-co im Block auf die Bereichsfarbe. Die .bk-*-Klassen selbst sind\n bereichsneutral. -->\n<div data-accent=\"ki\">\n\n <!-- Konfiguration am Formular: Preis pro Platz, Höchstzahl. novalidate, damit die\n Inline-Meldungen statt der nativen Browser-Bubbles erscheinen. -->\n <form id=\"bk-demo\" data-booking data-price=\"1290\" data-max=\"12\" novalidate>\n\n <!-- Gruppe = eine Entscheidung samt ihrer Folgen. Untergruppen gliedern\n innerhalb, verschachtelte fieldsets geben Screenreadern die Zugehörigkeit\n mit. -->\n <fieldset class=\"bk-fieldset\">\n <legend class=\"bk-legend\">Auftraggeber</legend>\n\n <fieldset class=\"bk-subgroup\">\n <legend class=\"bk-sublegend\">Anmeldung als</legend>\n <div class=\"seg seg-ki\">…</div>\n </fieldset>\n\n <!-- Bedingter Block: hidden statt disabled, als Untergruppe IN der Gruppe\n seines Auslösers. data-required markiert Felder, die nur im\n eingeblendeten Zustand Pflicht sind; main.js setzt required mit. -->\n <fieldset class=\"bk-subgroup\" data-bk=\"firma-block\" hidden>\n <legend class=\"bk-sublegend\">Firmendaten</legend>\n <input type=\"text\" id=\"bk-demo-firma\" data-required\n autocomplete=\"organization\" data-err=\"Bitte gib den Firmennamen an\">\n </fieldset>\n\n <fieldset class=\"bk-subgroup\">\n <legend class=\"bk-sublegend\">Kontaktperson</legend>\n <div class=\"bk-grid-2\"><!-- kollabiert unter 768 px einspaltig -->\n <div class=\"field\">…</div>\n </div>\n </fieldset>\n </fieldset>\n\n <!-- Repeater: Container + template. main.js klont pro Platz einen Block, vergibt\n id/for und autocomplete-Section nach Position (section-tn1, section-tn2, …). -->\n <input type=\"number\" data-bk=\"anzahl\" value=\"1\" min=\"1\" max=\"12\" required>\n <div class=\"bk-summary\" role=\"status\">…</div>\n <div data-bk=\"personen\"></div>\n <template data-bk=\"person-template\">\n <fieldset class=\"bk-person\"><!-- Gruppe, nicht nur Optik -->\n <legend class=\"bk-person-title\" data-bk=\"person-title\">Teilnehmende 1</legend>\n <input type=\"text\" data-bk=\"p-vorname\" data-ac=\"given-name\" required>\n </fieldset>\n </template>\n\n <!-- Fehlerübersicht am Formularkopf: bekommt nach gescheitertem Submit den\n Fokus. -->\n <div class=\"bk-errors\" data-bk=\"errors\" role=\"alert\" tabindex=\"-1\" hidden>\n <ul data-bk=\"error-list\"></ul>\n </div>\n\n <!-- Bestellübersicht unmittelbar vor dem Button. Leere Zeilen bleiben stehen\n und tragen data-empty, statt zu verschwinden. -->\n <div class=\"bk-order\">\n <dl class=\"bk-order-list\">\n <div><dt>Termin</dt><dd data-bk=\"order-termin\" data-empty>Noch nicht gewählt</dd></div>\n </dl>\n <div class=\"bk-order-total\">…</div>\n </div>\n\n <!-- Pflicht-Checkbox: data-bk=\"consent-required\" + eigene Fehlermeldung. Der\n verlinkte Hinweis ist ein echter Anker, kein span: sonst ist er nicht per\n Tab erreichbar und wird nicht als Link angesagt. -->\n <div class=\"bk-consent\">\n <input type=\"checkbox\" id=\"bk-demo-agb\" data-bk=\"consent-required\"\n data-err=\"Bitte akzeptiere die Teilnahme- und Stornobedingungen\">\n <label for=\"bk-demo-agb\">Ich akzeptiere die\n <a class=\"body-link\" href=\"/teilnahmebedingungen\">Teilnahme- und Stornobedingungen</a>.\n </label>\n </div>\n </form>\n</div>\n```\n\n## Bedingte Felder\n\nZwei Weichen blenden Feldblöcke ein und aus. Beide folgen derselben Mechanik: das\n`hidden`-Attribut am Block, `data-required` an den Feldern, die nur im sichtbaren\nZustand Pflicht sind.\n\n| Auslöser | Eingeblendet | Verhalten |\n|---|---|---|\n| Segmented Control „Firma“ | Fieldset Firmendaten | Firma wird Pflichtfeld. Bei „Privatperson“ verschwindet der Block samt Fehlerzustand |\n| Checkbox „Rechnung geht an eine andere Adresse“ | Fieldset Abweichende Rechnungsadresse | Empfänger, Straße, PLZ und Ort werden Pflichtfelder |\n\n| Regel | Begründung |\n|---|---|\n| `hidden` statt `disabled` | Deaktivierte Felder werden beim Submit nicht mitgesendet und von Screenreadern übersprungen. Ausgeblendete Felder behalten ihren Wert und kommen beim Wiedereinblenden unverändert zurück |\n| `required` mitschalten | Ein unsichtbares Pflichtfeld blockiert die Absendung ohne sichtbare Ursache. `data-required` ist die Merkmarkierung, `required` wird beim Einblenden gesetzt und beim Ausblenden entfernt |\n| Fehlerzustand beim Ausblenden aufräumen | Sonst bleibt eine `.error-msg` im DOM stehen und wird beim nächsten Einblenden erneut angesagt |\n| Block liegt in der Gruppe seines Auslösers | Ein bedingter Block ist keine eigene Gruppe, sondern eine `.bk-subgroup` innerhalb der Gruppe, die ihn schaltet. Als Geschwister-Gruppe mit eigener Trennlinie wirkt er unabhängig, obwohl er ohne seinen Auslöser gar nicht existiert |\n| Block bleibt an seiner Position | Der eingeblendete Block erscheint unter seinem Auslöser, nicht am Formularanfang. Kein Layout-Sprung, kein Suchen |\n| Fokus wandert nicht automatisch | Ein erzwungener Fokussprung nach dem Klick auf eine Checkbox reißt Tastatur- und Screenreader-Nutzende aus dem Kontext. Der nächste Tab landet ohnehin im neuen Block |\n\n## Teilnehmende\n\n**Warum die Anzahl ein eigenes Feld ist.** Ein reiner „Weitere Person\nhinzufügen“-Repeater leitet die Anzahl aus der Listenlänge ab. Für eine Buchung\nreicht das nicht, weil die Zahl kaufmännisch trägt: Sie bestimmt den Preis und muss\ngegen das Platzkontingent des Termins validiert werden (`min`/`max`). Deshalb ist die\nAnzahl das führende Feld, die Namensblöcke folgen ihr, und der Hinzufügen-Button\nerhöht sie, statt sie zu umgehen. So gibt es genau eine Quelle der Wahrheit.\n\n| Interaktion | Verhalten |\n|---|---|\n| Anzahl erhöhen | Es werden Blöcke am Ende ergänzt. Bereits eingegebene Namen bleiben unberührt |\n| Anzahl verringern | Nur leere Blöcke am Ende fallen weg. Enthält einer noch Daten, bleibt die Anzahl stehen und eine `.error-msg` nennt den betroffenen Block |\n| „Weitere Person hinzufügen“ | Erhöht die Anzahl um eins und setzt den Fokus in das erste Feld des neuen Blocks |\n| „Entfernen“ | Löscht genau diesen Block, nummeriert die übrigen neu, senkt die Anzahl und setzt den Fokus auf das Anzahl-Feld. Ab zwei Blöcken sichtbar |\n| „Ich nehme selbst teil“ | Block 1 übernimmt Vorname, Nachname und E-Mail aus dem Kontakt und folgt weiteren Änderungen, bis der Block von Hand bearbeitet wird. Die Felder bleiben editierbar. Solange der Haken sitzt, hat Block 1 keinen Entfernen-Button |\n| Preiszeile | `.bk-summary` mit `role=\"status\"`. Zeigt Plätze, Einzelpreis und Summe, aktualisiert bei jeder Änderung der Anzahl |\n| Blockgrenze | Jeder Block ist ein `<fieldset class=\"bk-person\">` mit `<legend>`. Ohne diese Gruppe hört ein Screenreader dreimal nur „Vorname“, ohne zu wissen, zu wem |\n\n**Warum keine Auto-Nummerierung per CSS-Counter.** Die Blocknummer steckt nicht nur\nin der Überschrift, sondern auch in `id`, `for` und der `autocomplete`-Section. Beim\nEntfernen eines mittleren Blocks werden alle drei über `renumber()` neu vergeben,\nwährend die Feldwerte mit ihrem DOM-Knoten mitwandern. Es wird nichts umkopiert,\ndeshalb kann dabei auch nichts verloren gehen.\n\n## Validierung & Barrierefreiheit\n\nDas Formular nutzt dasselbe Muster wie die Kontaktformulare in\n`Komponenten/Inputs & Forms/Textfeld/Verwendung`: `novalidate` unterdrückt die nativen\nBubbles, geprüft wird beim Submit, Fehler erscheinen als `.field.has-error` plus\n`.error-msg` mit `role=\"alert\"` unter dem Feld, der Fokus springt auf das erste\nungültige Feld. Die Helfer `setError()` und `clearError()` in `main.js` sind für\nKontakt- und Buchungsformular dieselben.\n\n**Feldspezifische Meldungen.** Jedes Pflichtfeld trägt sein `data-err` im Markup\n(„Bitte gib den Ort an“ statt „Dieses Feld ist erforderlich“). Bei einem Formular\ndieser Länge ist eine generische Meldung wertlos, sobald mehrere Fehler gleichzeitig\nstehen. Ausgeblendete Blöcke werden übersprungen, geprüft wird nur, was sichtbar\nist.\n\n**Fehlerübersicht statt Alert-Salve.** Das kurze Kontaktformular gibt jeder Meldung\nein `role=\"alert\"`, bei drei Feldern funktioniert das. Hier entstehen bei einem\nleeren Submit über zehn Meldungen gleichzeitig, und gleichzeitig eingefügte Alerts\nergeben beim Screenreader eine unbrauchbare Ansage. Deshalb sammelt `.bk-errors` am\nFormularkopf alle Fehler als Sprungliste, trägt selbst das `role=\"alert\"` und\nbekommt den Fokus. Die Meldungen am Feld bleiben über `aria-describedby` verbunden\nund werden beim Fokussieren gelesen, tragen aber kein eigenes `role=\"alert\"` mehr\n(Parameter `quiet` an `setError()`).\n\n**Nachvalidierung.** Erst geprüft wird beim Absenden, nicht beim Tippen, damit\nniemand beim Ausfüllen angemeckert wird. Nach einem gescheiterten Submit kippt das\nFormular in den Nachvalidierungs-Modus: Jede Korrektur räumt die Meldung am Feld und\nden zugehörigen Eintrag in der Übersicht sofort weg.\n\n| Aspekt | Regel |\n|---|---|\n| Gruppierung | Jeder Abschnitt und jeder Teilnehmendenblock ist ein `<fieldset>` mit `<legend>`. Screenreader sagen die Gruppe vor dem Feld an, dadurch bleibt „Vorname“ in Block 3 unterscheidbar von „Vorname“ im Kontakt |\n| Pflicht-Checkboxen | `aria-required=\"true\"` an der Box selbst. Das sichtbare `*` im Label ist `aria-hidden`, ohne das Attribut wäre die Pflicht rein optisch |\n| Verlinkte Bestätigungen | Teilnahmebedingungen und Datenschutzhinweise sind echte `<a class=\"body-link\">` im Label, kein `<span>` |\n| Fehlerübersicht | `.bk-errors` mit `role=\"alert\"` und `tabindex=\"-1\"`, wird nach einem gescheiterten Submit fokussiert. Die Einträge sind Sprunglinks auf das jeweilige Feld |\n| Autofill | Durchgängige `autocomplete`-Tokens. Die abweichende Rechnungsadresse trägt `section-rechnung`, jeder Teilnehmendenblock `section-tn1`, `section-tn2` und so weiter, damit der Browser die Blöcke nicht gegenseitig überschreibt (WCAG 1.3.5) |\n| Anzahl und Preis | `.bk-summary` trägt `role=\"status\"`. Jede Änderung von Anzahl, Termin oder Personenliste wird dadurch angesagt, ohne den Fokus zu stören |\n| Fokusführung | Hinzufügen setzt den Fokus in den neuen Block, Entfernen zurück auf das Anzahl-Feld |\n| Entfernen-Button | `aria-label=\"Teilnehmende 3 entfernen\"`, weil „Entfernen“ allein in einer Liste gleichlautender Buttons mehrdeutig ist |\n| Pflichtfelder | Sichtbares `*` (`aria-hidden`) plus `aria-required=\"true\"`. Der Hinweis „Mit * markierte Felder sind Pflichtfelder“ steht vor dem ersten Feld |\n| Bestellübersicht | Bewusst ohne `role=\"status\"`, sie fasst nur zusammen, was im Formular schon steht. Semantisch eine `<dl>`, damit Bezeichnung und Wert paarweise gelesen werden |\n| Erfolgszustand | `.bk-success` mit `role=\"status\"` und `tabindex=\"-1\"`, wird nach dem Absenden fokussiert. Das Formular geht auf `hidden` |\n| Touch & Tastatur | Alle Felder und Buttons ab 44 px Höhe, native Radios und Checkboxen mit `accent-color`, Fokusring über `--focus-ring` |\n\n## Anfrage oder Direktbuchung\n\nIm System gibt es zwei Wege, wie aus Interesse ein Termin wird. Sie schließen sich\nnicht aus, sie gehören zu unterschiedlichen Angeboten. Die Entscheidung fällt am\nAngebot, nicht am Formular.\n\n| Kriterium | Anfrage (adaptive Kontaktseite) | Direktbuchung (dieses Formular) |\n|---|---|---|\n| Preis | individuell, wird im Gespräch bestimmt | fester Listenpreis pro Platz, sofort berechenbar |\n| Verfügbarkeit | offen, Termin entsteht erst | feste Durchführungen mit Platzkontingent |\n| Rechtsfolge | unverbindlich | verbindlich, Submit-Label benennt die Zahlungspflicht |\n| Datenbedarf | Name, E-Mail, Anliegen | zusätzlich Rechnungsanschrift und Teilnehmendenliste |\n| Typische Fälle | Inhouse-Training, Beratung, Workshop nach Zuschnitt | offenes Seminar mit veröffentlichter Terminliste |\n\nDie Beispielseite Seminar (`Seitenmuster/Seminar · Training`) bleibt bewusst beim\nAnfrage-Flow: Die Termin-Zeilen führen über `data-k-bereich` und `data-k-anliegen`\nauf die Kontaktseite. Das Buchungsformular ist die Variante für Angebote, bei denen\nPreis und Kontingent feststehen.\n\n## Für die Umsetzung\n\nDie Referenz-Demo in der Doku-Site ist vollständig bedienbar, aber ein Mockup ohne\nBackend. Wer die Komponente in die echte Site überführt, ob Mensch oder Assistent,\nbraucht die folgenden Punkte als Anforderungen, nicht als Ideen.\n\n**Wichtigster Punkt: Schutz gegen Datenverlust.** Ein ausgefülltes Formular enthält\nbis zu zwölf Teilnehmende mit je drei Feldern, dazu Kontakt und zwei Anschriften. Ein\nversehentliches Zurück, ein geschlossener Tab oder ein abgestürzter Browser wirft\nalles weg. Das Referenz-Markup hat keinen Zwischenspeicher; in Produktion ist er\nPflicht, nicht optional.\n\n| Anforderung | Umsetzung |\n|---|---|\n| Entwurf laufend sichern | Bei jedem `input` und `change` den Formularstand entprellt (ca. 500 ms) unter einem stabilen Schlüssel ablegen, zum Beispiel `booking-draft:<formular-id>` |\n| Wo ablegen | `sessionStorage` für den laufenden Tab. `localStorage` nur, wenn der Entwurf einen Browserneustart überleben soll, dann mit Ablaufdatum. Keine Zahlungs- oder Ausweisdaten, und Namen sowie Anschriften sind personenbezogen: Aufbewahrungsdauer und Rechtsgrundlage vorher mit dem internen Datenschutzbeauftragten klären (Michael Didion / Harry Ursinus, datenschutz@conciso.de) |\n| Wiederherstellen mit Ansage | Beim Laden nicht still befüllen. Einen Hinweis über dem Formular zeigen, mit der Möglichkeit, den Entwurf zu verwerfen |\n| Entwurf verwerfen | Nach erfolgreichem Submit den Schlüssel löschen, sonst taucht die alte Buchung bei der nächsten wieder auf |\n| Warnung beim Verlassen | `beforeunload` nur registrieren, solange es ungespeicherte Änderungen gibt, und nach dem Submit wieder abmelden |\n| Reihenfolge beim Wiederherstellen | Erst Kundentyp und die Checkbox für die abweichende Rechnungsadresse setzen, dann die bedingten Blöcke einblenden, dann die Anzahl setzen (das erzeugt die Teilnehmendenblöcke), erst danach die Werte der Personen eintragen |\n| Nicht mit „Ich nehme selbst teil“ kollidieren | Die Übernahme aus den Kontaktdaten läuft nur, solange ein Feld nicht von Hand bearbeitet wurde. Beim Wiederherstellen deshalb auch dieses Merkmal mitsichern |\n\n| Thema | Anforderung |\n|---|---|\n| Serverseitige Validierung | Die Prüfungen im Browser sind Komfort, keine Absicherung. Jede Regel muss serverseitig gespiegelt werden, inklusive der bedingten Pflichtfelder |\n| Platzkontingent | `data-max` ist im Referenz-Markup fest. Real kommt die Obergrenze pro Termin aus dem Backend und muss beim Submit erneut geprüft werden. Ausverkaufte Termine gehören nicht in den Select |\n| Doppelte Buchungen | Submit-Button nach dem ersten Klick sperren und die Anfrage mit einem Idempotenzschlüssel versehen |\n| Preis | `data-price` ist Anzeige, nie Berechnungsgrundlage. Die Summe entsteht serverseitig aus dem Termin |\n| Bestellübersicht | Welche Angaben bei einer zahlungspflichtigen Buchung zwingend unmittelbar vor dem Absenden stehen müssen, vor dem Livegang rechtlich prüfen lassen |\n| Ziele der Rechtstexte | Real zeigen die Consent-Links auf die veröffentlichten Seiten und öffnen sie so, dass das ausgefüllte Formular nicht verloren geht (`target=\"_blank\"` mit `rel=\"noopener\"` oder ein Overlay) |\n| Bereichsfarbe | `data-accent=\"ki|es|wo\"` sitzt am Container um das Formular, nicht am Formular selbst und nicht an den einzelnen Elementen |\n| Spam | Kein sichtbares Captcha als erste Wahl. Zeitmessung und ein verstecktes Honeypot-Feld reichen für ein Formular dieser Länge meist aus und kosten keine Barrierefreiheit |\n\n## Verwendung\n\n<div className=\"doc-eyebrow\">Dos & Don'ts</div>\n\n**✓ Tun**\n\n- Anzahl und Namensblöcke synchron halten, mit der Anzahl als einziger Quelle der Wahrheit\n- Bedingte Blöcke ein- und ausblenden statt Felder zu deaktivieren, und `required` mitschalten\n- Die Rechnungsanschrift des Anmelders als Standard setzen, die Abweichung als Opt-in anbieten\n- Das Submit-Label die Rechtsfolge benennen lassen, hier „Zahlungspflichtig buchen“\n- Jedem Pflichtfeld eine eigene Fehlermeldung über `data-err` geben\n- Ab etwa fünf Pflichtfeldern eine Fehlerübersicht zeigen und sie fokussieren, statt nur auf das erste Feld zu springen\n- Fehler beim Korrigieren sofort wieder wegräumen, am Feld und in der Übersicht\n- Gruppenüberschrift und Feldlabel typografisch klar trennen, sonst ist die Gliederung nur semantisch da\n- Eine Gruppe als eine Entscheidung samt ihrer Folgen behandeln, nicht als Themenwort\n- Vor dem zahlungspflichtigen Button zusammenfassen, was gebucht wird, nicht nur den Betrag\n- Den Bereich einmal über `data-accent` am umgebenden Container setzen, dann tönen auch die Links im Formular mit\n- Verlinkte Rechtstexte in den Bestätigungen als echten `<a>` auszeichnen\n\n**✕ Nicht tun**\n\n- Die Anzahl reduzieren und dabei ausgefüllte Namensblöcke still verwerfen\n- Teilnehmendenblock 1 nach „Ich nehme selbst teil“ auf `disabled` setzen, die Werte gingen beim Submit verloren\n- Firmen- und Rechnungsfelder auch Privatpersonen zeigen, nur mit „(optional)“ entschärft\n- Unverbindliche Anfrage und zahlungspflichtige Buchung in einem Formular mischen\n- Das Formular in einen Stepper zerlegen, nur um es kürzer wirken zu lassen\n- Ein Dutzend Meldungen mit `role=\"alert\"` gleichzeitig einfügen, das ergibt eine unverständliche Ansage\n- Die Teilnehmendenblöcke nur optisch durch eine Überschrift trennen, ohne `<fieldset>`\n- Pflichtsternchen und „(optional)“ gleichzeitig setzen, eine der beiden Auszeichnungen reicht\n- Ein freiwilliges Marketing-Häkchen optisch gleichrangig unter die Pflicht-Bestätigungen setzen\n- Eine Trennlinie zwischen eine Weiche und die Felder legen, die sie schaltet\n- Einzelnen Links im Formular eine Inline-Bereichsfarbe geben, das bricht im Dark Mode\n- Den verlinkten Rechtstext als `<span>` mit `cursor:pointer` bauen, er ist dann für Tastatur und Screenreader kein Link\n\n## Verwandte Seiten\n\n- `Komponenten/Inputs & Forms/*` (Textfeld, Auswahlfeld, Checkbox, Radio)\n- `Komponenten/Dropdowns/*`\n- `Komponenten/Buttons/Button`\n- Doku-Sektion: `docs/index.html#sec-booking`\n",
13
13
  "summary": "# Buchungsformular Verbindliche Buchung eines konkreten Termins, am Beispiel eines Seminar..."
14
14
  }
15
15
  }
@@ -0,0 +1,18 @@
1
+ {
2
+ "components": {
3
+ "komponenten-call-to-action-cta-band": {
4
+ "id": "komponenten-call-to-action-cta-band",
5
+ "name": "komponenten-call-to-action-cta-band",
6
+ "docs": {
7
+ "komponenten-call-to-action-cta-band--verwendung": {
8
+ "id": "komponenten-call-to-action-cta-band--verwendung",
9
+ "name": "Verwendung",
10
+ "path": "./src/docs/komponenten/cta-verwendung.mdx",
11
+ "title": "Komponenten/Call to Action/CTA-Band",
12
+ "content": "import { Meta } from '@storybook/addon-docs/blocks';\nimport * as CtaBandStories from '../../lib/cta-band/cta-band.stories';\n\n<Meta of={CtaBandStories} name=\"Verwendung\" />\n\n# Verwendung\n\nCall to Action deckt zwei Pattern-Familien ab: das Page-End-CTA-Band als letzte, konkrete\nEinladung am Ende jeder Customer-Page, und vier Download-CTA-Varianten für\nRessourcen-Downloads. Ergänzend dokumentiert diese Seite den Editorial Split als\nsekundäre Konversionsfläche neben dem primären Page-End-CTA. Das Page-End-CTA-Band hat\nseit Ticket 07 der Seitenbausteine-Serie ein eigenes Bauteil (`CtaBand`, Attributselektor\n`[cdsCtaBand]`), ebenso die vier Download-Varianten (`DownloadCta`); der Editorial Split\nbleibt ein CSS-Rezept ohne Angular-Komponente.\n\n## Wann welche Variante\n\n| Variante | Bauteil | Wann einsetzen |\n|---|---|---|\n| Page-End-CTA-Band | `CtaBand` (`[cdsCtaBand]`) | Letzte Einladung am Ende jeder Customer-Page, direkt vor dem Footer, eine Aktion auf der Bereichsfarbe als Hintergrund |\n| Editorial Split | bauteillos (CSS-Rezept `.ep-section` + `.layout-grid`) | Sekundäre oder tertiäre Konversionsfläche neben dem primären Page-End-CTA (zum Beispiel ein Karriere-Block auf der Landing), ohne Card-Chrome |\n| Download · Hero | `DownloadCta` | Hauptressource einer Seite, Landing Pages, Ressourcenseiten, Kampagnen, maximal einmal pro Seite |\n| Download · Mit Vorschaubild | `DownloadCta` | Whitepapers und Reports, deren Deckblatt als Vorschau den Inhalt sofort greifbar macht |\n| Download · Kompakt | `DownloadCta` | Ressourcenlisten mit mehreren Downloads (zum Beispiel eine Mediathek) |\n| Download · Minimal | `DownloadCta` | Quellenangaben, Fließtext-Links oder Nebenbereiche (Footer, Blog, Dokumentation) |\n\n### Page-End-CTA-Band (`CtaBand`)\n\nAttributselektor `[cdsCtaBand]` statt eigenem Element: die Bandfläche ist im Mockup\nausnahmslos ein Inline-Style direkt am Element (`.ep-cta-band`, Padding\n`var(--s12) var(--s8)`, zentriert) — ein eigenes Element würde denselben „Fläche am\nHost“-Fehler reproduzieren, den `docs/adr/0008` für `cds-section` gemessen hat. Der\nKonsument setzt Hintergrund und Schrift deshalb\nweiterhin selbst, auf demselben `<div cdsCtaBand>`, das die Komponente trägt:\nBereichsfarbe (`--co-700` / `--ki-800` / `--es-700` / `--wo-700`), Schrift `color:#fff`.\nAuf der dunklen Fläche wird der Filled-Button über die zusammengesetzte Modifier-Klasse\n`.btn-on-band` invertiert (weißer Hintergrund, Text in der Bereichsfarbe) — die\nKomponente setzt diese Klassen direkt (`cds-button` kennt zwar den Modifier, kann aber\nkeinen `<a>` rendern, siehe `cta-band.component.ts`). Die Aktion führt in der\nRegel auf die adaptive Kontaktseite (`data-ep=\"kontakt\"`), nie auf ein Overlay oder\nModal. Bewusst nur EINE Aktion, kein `secondaryLabel`: keines der 22\nMockup-Vorkommen zeigt eine zweite, und `.btn-on-band` lässt sich mit den\nvorhandenen CSS-Klassen ohnehin nicht in einer zurückhaltenderen zweiten Variante\nbauen (jede Nicht-`filled`-Variante würde Text in Bandfarbe auf Bandfarbe zeigen —\nfestgehalten in\n`.scratch/angular-seitenbausteine/issues/16-css-luecke-zweite-aktion-auf-band.md`).\n\n```html\n<div cdsCtaBand\n heading=\"Erstgespräch, 30 Minuten, kostenfrei.\"\n sub=\"Du schilderst Deine Situation, wir geben eine erste Einschätzung.\"\n primaryLabel=\"Termin buchen\"\n primaryHref=\"/kontakt\"\n style=\"background:var(--co-700);color:#fff\">\n</div>\n```\n\n### Editorial Split (bauteillos)\n\nRuhige 2-Spalten-Aufteilung in einer normalen `.ep-section`: links ein Foto im\n4:3-Format mit `--r-md`, rechts Eyebrow, H2, Lead und Button. Kein Card-Container, kein\nSchatten, keine Border. Bühne entsteht über die Hintergrundfarbe der Sektion\n(typischerweise `--co-50` oder `--n-50`) und über das Foto links, nicht über\nCard-Chrome, das dem Markenwert „Ruhig“ widerspräche.\n\n```html\n<div class=\"ep-section\" style=\"background:var(--co-50)\">\n <div class=\"layout-grid\" style=\"align-items:center\">\n <div class=\"col-6\">\n <img src=\"bild.jpg\" alt=\"Beschreibend\"\n style=\"width:100%;aspect-ratio:4/3;object-fit:cover;border-radius:var(--r-md)\">\n </div>\n <div class=\"col-6\">\n <div class=\"ep-section-label t-co\">Karriere</div>\n <h2 class=\"ep-section-h2\">Dein Fußabdruck bei Conciso.</h2>\n <p class=\"ep-section-sub\">Projekte mit Wirkung, ein Team, das füreinander einsteht.</p>\n <button class=\"btn btn-filled btn-co\">Stellen entdecken</button>\n </div>\n </div>\n</div>\n```\n\n## Barrierefreiheit\n\n| Aspekt | Regel |\n|---|---|\n| Button-Label | `aria-label` mit Dateiname und Format auf dem Download-Button setzen (zum Beispiel „Figma-Bibliothek herunterladen (Figma, 48 MB)“), der Text „Herunterladen“ allein nennt kein Ziel |\n| Format- und Größenangabe | Dateiformat und Dateigröße immer sichtbar im Markup, nicht nur im Button-Label |\n| Brand Area | Bereichsfarbe des zugehörigen Dokuments nutzen, keine bereichsfremde Farbe |\n| Kontrast auf Bereichsfläche | Der Filled-Button wird auf der farbigen Fläche des Page-End-CTA-Band über `.btn-on-band` invertiert (weißer Hintergrund, Text in Bereichsfarbe); ohne Inversion würde der Button auf der eigenen Bereichsfarbe stehen und Kontrast verlieren |\n\n## Dos & Don'ts\n\n**Tun**\n\n- Dateiformat und -größe immer angeben, Nutzer sollen vor dem Download wissen, was sie erhalten\n- Brand-Area-Farbe des zugehörigen Dokuments verwenden, stärkt die visuelle Zugehörigkeit\n- `aria-label` auf dem Button setzen, zum Beispiel „Figma-Bibliothek herunterladen (Figma, 48 MB)“\n- Pro Seite maximal einen Hero-CTA, mehrere Hero-Blöcke erzeugen visuelle Konkurrenz\n\n**Nicht tun**\n\n- Download-Button ohne Format und Größe, erhöht die kognitive Last und bricht Vertrauen\n- Hero-Variante für unwichtige Sekundär-Downloads, die Größe signalisiert Wichtigkeit\n- Generisches „Herunterladen“ ohne Dateinamen, schlecht für Screenreader und Kontext\n- Bereichsfremde Farbe für ein Dokument verwenden, bricht den thematischen Zusammenhang\n\n## Verwandte Seiten\n\n- Komponenten/Call to Action/CTA-Band (Story, deckt `CtaBand` ab)\n- Komponenten/Call to Action/DownloadCta (Story, deckt alle vier Download-Varianten ab)\n- Doku-Sektion: `docs/index.html#gt-cta-band` (Page-End-CTA-Band)\n- Doku-Sektion: `docs/index.html#gt-cta-editorial` (Editorial Split)\n",
13
+ "summary": "# Verwendung Call to Action deckt zwei Pattern-Familien ab: das Page-End-CTA-Band als letz..."
14
+ }
15
+ }
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "components": {
3
+ "komponenten-cards-teaser-card": {
4
+ "id": "komponenten-cards-teaser-card",
5
+ "name": "komponenten-cards-teaser-card",
6
+ "docs": {
7
+ "komponenten-cards-teaser-card--verwendung": {
8
+ "id": "komponenten-cards-teaser-card--verwendung",
9
+ "name": "Verwendung",
10
+ "path": "./src/docs/komponenten/cards-verwendung.mdx",
11
+ "title": "Komponenten/Cards & Teaser/Card",
12
+ "content": "import { Meta, Unstyled } from '@storybook/addon-docs/blocks';\nimport * as CardStories from '../../lib/card/card.stories';\n\n<Meta of={CardStories} name=\"Verwendung\" />\n\n# Verwendung\n\nGenerische Karten (statisch, Link-Karte) und Inhaltskarten (Stat), alle Brand Areas, barrierefrei. Der Abschnitt deckt mehr ab, als es eigene Bauteile gibt: die editoriale Kennzahl-Zeile ist ein reines CSS-Rezept ohne eigene Komponente. Klickbare Karte, Featured-Karte, Icon-Karte, offene Feature-Liste und Tier-Trenner haben jeweils ein eigenes Angular-Bauteil (siehe „Verwandte Seiten“).\n\n## Anatomie\n\nDie folgende Anatomie zeigt die fünf Bausteine einer generischen Karte: Media-Bereich, Eyebrow, Title, Text und Footer. Die Ziffern korrespondieren mit den Begriffen, die in den übrigen Abschnitten dieser Seite verwendet werden.\n\n<Unstyled>\n <div style={{ border: 'var(--bd-strong)', borderRadius: 'var(--r-lg)', overflow: 'hidden' }}>\n <div\n style={{\n height: 60,\n background: 'var(--co-50)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n borderBottom: 'var(--bd)',\n }}\n >\n <span style={{ font: '400 12px/16px var(--font)', color: 'var(--tx-secondary)' }}>\n ① Media, Bereichsfarbe <code className=\"token\">-50</code>\n </span>\n </div>\n <div style={{ padding: 'var(--s4)' }}>\n <div\n className=\"t-co\"\n style={{\n font: '500 12px/16px var(--font)',\n letterSpacing: '.08em',\n textTransform: 'uppercase',\n marginBottom: 'var(--s1)',\n }}\n >\n ② Eyebrow, Bereichsname\n </div>\n <div\n style={{\n font: '500 16px/24px var(--font)',\n color: 'var(--tx-primary)',\n marginBottom: 'var(--s1)',\n }}\n >\n ③ Title, Kernaussage\n </div>\n <div style={{ font: 'var(--ty-body-md)', color: 'var(--tx-secondary)', marginBottom: 'var(--s4)' }}>\n ④ Text, max. 2 Sätze, konkret\n </div>\n <div\n style={{\n display: 'flex',\n justifyContent: 'flex-end',\n gap: 'var(--s2)',\n borderTop: 'var(--bd)',\n paddingTop: 'var(--s3)',\n }}\n >\n <span style={{ font: '400 12px/16px var(--font)', color: 'var(--tx-muted)' }}>\n ⑤ Footer, max. 2 Aktionen\n </span>\n </div>\n </div>\n </div>\n</Unstyled>\n\n## Wann welche Variante\n\n| Variante | Klasse | Wann einsetzen | Elevation |\n|---|---|---|---|\n| Statisch | `.card` | Der Standard, Karten deren Aktionen in Footer-Buttons liegen, dazu Listen- und Tabellen-Kontexte ohne visuelle Schwere | `--e0` + Rahmen |\n| Link-Karte | `a.card.card-elevated` | Wenn die ganze Karte zu einem Ziel führt: Listing-Teaser, Beitrags-, Job- und Veranstaltungs-Karten | `--e1`, Hover `--e2` + Lift |\n\n**Klickbare Karte als Modus.** Wenn die ganze Karte zu einem Ziel führen soll, gibt es zwei Patterns, je nach Karten-Typ und Inhaltsdichte:\n\n| Pattern | Wrapping | Wann einsetzen |\n|---|---|---|\n| `.ep-card.ep-card-link` | `<a class=\"ep-card ep-card-link\">` | Kompakte Teaser-Kacheln mit Icon, Eyebrow, Titel, kurzem Text und CTA-Pfeil-Zeile, ohne Card-Footer-Buttons |\n| `a.card.card-elevated` | `<a class=\"card card-elevated\">` | Textreiche Listing-Cards mit Card-Media, Card-Body (Pill, Titel, Text, Meta-Zeile) und implizitem „Beitrag lesen →“-Anker am Ende |\n| `a.card.card-elevated.card-featured` | `<a class=\"card card-elevated card-featured\">` | Horizontale Großkarte für genau einen hervorgehobenen Beitrag oder Termin, Bild links im 16:9, Content rechts |\n\nIn Angular deckt jedes der drei Patterns ein eigenes Bauteil ab: `<a cdsIconCard>` (Icon-Karte), `cds-link-card` (Klickbare Karte) und `cds-featured-card` (Featured-Karte).\n\n**Weitere CSS-Rezepte ohne eigenes Bauteil**, sinnvoll dort, wo ein weiteres Karten-Grid die Seite überladen würde:\n\n| Muster | Rolle | Wann statt Karte |\n|---|---|---|\n| Stat-Karte (`.card-stat`) | Eigenständige KPI-Kachel mit Rahmen | Kennzahlen auf Landingpages, Jahresberichten, Dashboards |\n| Bandstreifen (`.card-stat-strip` / `.card-stat-flat`) | Flache Variante ohne Rahmen und Schatten | Kompakte KPI-Bänder, Footer-Stats, Hero-Anschluss-Sektionen |\n| Editoriale Kennzahl-Zeile | Redaktioneller Fließinhalt, nur durch Haarlinien getrennt, kein Karten-Chrome | Case-, Projekt- und Leistungslisten, wenn auf der Seite bereits Karten-Grids stehen |\n| Offene Feature-Liste (`.ep-feature`, in Angular `<div cdsFeature>`) | Icon-geführte Aufzählung ohne Rahmen, Schatten oder Media-Fläche | Vollständiger Funktionsumfang, wenn ein weiteres Karten-Grid überladen würde |\n\nKarten sind für Angebots-Menüs und Teaser da: Lösungs- und Leistungskacheln, Artikel-Vorschauen, Elemente, die zu einem Ziel führen und als abgeschlossene, klickbare Einheit funktionieren. Für Stimmen, Funktions- und Case-Listen ist meist eine offene, redaktionelle Darstellung die bessere Wahl: direkt auf der Fläche, durch Haarlinien getrennt. Das hält inhaltsdichte Abschnitte luftig und vermeidet den Eindruck einer Kachelwand, wenn mehrere Karten-Grids aufeinanderfolgen.\n\n**Testimonial-Karte**: wohnt unter `Komponenten/Zitate & Testimonials/Testimonial`, hier nur der Querverweis. Zitat mit Quellenangabe, farbiger Top-Border signalisiert die Brand Area, Auszeichnung über `<figure>` + `<blockquote>` für korrekte Semantik.\n\n**Warum oben und nicht links?** Die Leserichtung ist von links nach rechts, von oben nach unten, der oberste Kartenrand ist der erste Fixpunkt beim Scannen einer Seite. Die Linie links eignet sich für redaktionellen Fließinhalt (Blockquote), weil der Text direkt daneben beginnt und die Linie als Begleiter wirkt. Bei einer abgeschlossenen Karte würde ein linker Rand mit dem Layout-Raster kollidieren und die Außenkante der Karte unruhig machen.\n\n<Unstyled>\n <div className=\"layout-grid\">\n <div className=\"col-4\">\n <div\n style={{\n font: '500 12px/16px var(--font)',\n color: 'var(--tx-muted)',\n marginBottom: 'var(--s2)',\n }}\n >\n Mit Media-Bereich → keine Linie\n </div>\n <div style={{ border: 'var(--bd-strong)', borderRadius: 'var(--r-lg)', overflow: 'hidden' }}>\n <div\n style={{\n height: 40,\n background: 'var(--co-50)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <span style={{ font: '400 12px/16px var(--font)', color: 'var(--tx-secondary)' }}>\n Media\n </span>\n </div>\n <div style={{ padding: 'var(--s3) var(--s4)' }}>\n <div\n style={{\n height: 6,\n borderRadius: 3,\n background: 'var(--n-200)',\n marginBottom: 6,\n width: '55%',\n }}\n />\n <div style={{ height: 8, borderRadius: 3, background: 'var(--n-100)', marginBottom: 4 }} />\n <div style={{ height: 8, borderRadius: 3, background: 'var(--n-100)', width: '80%' }} />\n </div>\n </div>\n </div>\n <div className=\"col-4\">\n <div\n style={{\n font: '500 12px/16px var(--font)',\n color: 'var(--tx-muted)',\n marginBottom: 'var(--s2)',\n }}\n >\n Ohne Media-Bereich → Linie oben\n </div>\n <div\n style={{\n border: 'var(--bd-strong)',\n borderTop: '4px solid var(--co-500)',\n borderRadius: 'var(--r-lg)',\n overflow: 'hidden',\n }}\n >\n <div style={{ padding: 'var(--s3) var(--s4)' }}>\n <div\n style={{\n height: 6,\n borderRadius: 3,\n background: 'var(--n-200)',\n marginBottom: 6,\n width: '55%',\n }}\n />\n <div style={{ height: 8, borderRadius: 3, background: 'var(--n-100)', marginBottom: 4 }} />\n <div style={{ height: 8, borderRadius: 3, background: 'var(--n-100)', width: '80%' }} />\n </div>\n </div>\n </div>\n </div>\n</Unstyled>\n\n## Barrierefreiheit\n\n| Aspekt | Regel |\n|---|---|\n| Semantik | `<article>` für eigenständige Cards, Screenreader listen Artikel als navigierbare Landmark, Überschriften-Hierarchie einhalten (Card-Titel als `h3` unter einer Section-`h2`) |\n| Klickbereich | Wenn die ganze Karte zu einem Ziel führt: das umschließende Element auf `<a>` oder `<button>` umstellen, nie `<div>` mit `onclick`. Pfeil-Suffix in `<span aria-hidden=\"true\">` hüllen, Focus-Ring sichtbar lassen. Trägt die Karte nur passive Information, gehören Interaktionselemente ausschließlich in den Card-Footer |\n| Bilder & Icons | Card-Media-Bilder mit beschreibendem `alt`-Text versehen, dekorative SVGs mit `aria-hidden=\"true\"` + `focusable=\"false\"` ausblenden |\n\n**Bildslots sind 16/9 und kommen aus `.card-media`.** Listing-Grid und Featured-Card teilen sich das Verhältnis, damit Redaktion pro Beitrag ein Bild in einem Zuschnitt pflegt statt einen je Slot. Den Bildausschnitt setzt `object-position` am `<img>`, nicht `background-position` an einem Container, sonst gilt der Zuschnitt nur in einem Slot. Eigene Verhältnisse haben nur Hero und Slider (21/9) sowie Avatare (1/1). Reicht die Höhe nicht, wird Text gekappt, nicht das Verhältnis gedehnt.\n\n**Der Abstand gehört dem Container, nicht dem Kind.** `.card-body` ist eine Flex-Spalte mit `gap: var(--s3)`, das ist der einzige vertikale Rhythmus der Karte. `.card-eyebrow`, `.card-cta-link` und die Pill setzen im Card-Body keine eigene Marge, sonst addiert sie sich zum `gap`, statt zu kollabieren, denn im Flex-Layout kollabieren Margen nicht. Braucht eine Karte mehr Luft, wird nur der `gap` überschrieben, nicht eine Marge nachgeschoben.\n\n## Dos & Don'ts\n\n**Tun**\n\n- Eyebrow und Media konsequent in der Bereichsfarbe halten, die Karte ist immer einem Bereich zugehörig.\n- Schatten nur auf Link-Karten (`a.card.card-elevated`), Karten mit Aktionen im Footer ruhen flach mit Rahmen.\n- Max. 2 Aktionen im Card-Footer: Text-Button (sekundär) plus Filled-Button (primär).\n- Card-Text auf max. 2 Sätze begrenzen, prägnant und scanbar, kein Fließtext.\n- Gleichartige Karten in einem Grid konsistent halten, gleiche Variante, gleiche Breite.\n\n**Nicht tun**\n\n- Statische und Link-Karten in derselben Reihe mischen, der Schatten liest sich dann als Zufall statt als Klick-Angebot.\n- Mehr als 2 Filled Buttons im Footer, die Hierarchie wird unklar, eine Hauptaktion reicht.\n- Bereichsfarben einer Karte und ihrer Nachbarkarte mischen, jede Karte gehört zu einem Bereich.\n- Eine Karte erhöhen, deren Fläche nirgendwohin führt, das verspricht Klickbarkeit, die es nicht gibt.\n- Langen Fließtext in Card-Body, stattdessen einen Teaser formulieren und auf eine Detailseite verlinken.\n\n## Verwandte Seiten\n\n- Card (`Komponenten/Cards & Teaser/Card`)\n- Klickbare Karte (`Komponenten/Cards & Teaser/Klickbare Karte`), `cds-link-card`\n- Featured-Karte (`Komponenten/Cards & Teaser/Featured-Karte`), `cds-featured-card`\n- Icon-Karte (`Komponenten/Cards & Teaser/Icon-Karte`), `[cdsIconCard]`\n- Feature-Liste (`Komponenten/Cards & Teaser/Feature-Liste`), `[cdsFeature]`\n- Tier-Trenner (`Komponenten/Cards & Teaser/Tier-Trenner`), `cds-tier`\n- StatCard (`Komponenten/Cards & Teaser/StatCard`)\n- StatStrip (`Komponenten/Cards & Teaser/StatStrip`)\n- Testimonial (`Komponenten/Zitate & Testimonials/Testimonial`), Zitat-Karte mit Quellenangabe, hier nur verlinkt\n",
13
+ "summary": "# Verwendung Generische Karten (statisch, Link-Karte) und Inhaltskarten (Stat), alle Brand..."
14
+ }
15
+ }
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "components": {
3
+ "komponenten-chips-badges-pills-chip": {
4
+ "id": "komponenten-chips-badges-pills-chip",
5
+ "name": "komponenten-chips-badges-pills-chip",
6
+ "docs": {
7
+ "komponenten-chips-badges-pills-chip--verwendung": {
8
+ "id": "komponenten-chips-badges-pills-chip--verwendung",
9
+ "name": "Verwendung",
10
+ "path": "./src/docs/komponenten/chips-verwendung.mdx",
11
+ "title": "Komponenten/Chips, Badges & Pills/Chip",
12
+ "content": "import { Meta } from '@storybook/addon-docs/blocks';\nimport * as ChipStories from '../../lib/chip/chip.stories';\n\n<Meta of={ChipStories} name=\"Verwendung\" />\n\n# Verwendung\n\nDrei Formen, die leicht verwechselt werden: interaktive Filter-Chips (Toggle), passive Status- und Bereichs-Badges und redaktionelle Pills, alle je Brand Area farblich codiert (Bereichsreihenfolge co · ki · es · wo).\n\n## Wann welche Variante\n\n| Form | Interaktion | Rolle | Label |\n|---|---|---|---|\n| Chip | Interaktiv, Toggle über `aria-pressed=\"true/false\"` | Aktive Filter- und Auswahlkomponente | Max. 3 Wörter, prägnante Kategorie- oder Filterbegriffe |\n| Badge | Passiv, kein Klickverhalten | Status- oder Bereichskennzeichnung neben einem Element | 1 bis 2 Wörter, kein Verb |\n| Pill | Passiv, kein Klickverhalten | Redaktioneller Eyebrow vor einem Titel, ordnet einen Inhalt einer Brand Area zu | 1 bis 3 Wörter, ausschließlich der Bereichsname |\n\n**Abgrenzung Pill gegen Bereichs-Badge**: beide nutzen den 50er-Hintergrund und den 700er- beziehungsweise 800er-Text der Brand Area, beide haben die abgerundete Form.\n\n| Aspekt | Pill | Bereichs-Badge |\n|---|---|---|\n| Rolle | Redaktionelle Meta-Markierung, ordnet einen Inhalt einer Brand Area zu, oft als Eyebrow vor Titeln | Status- oder Bereichs-Kennzeichnung neben Inhalts-Elementen (z. B. neben einem Modulnamen) |\n| Typografie | label-xs, uppercase, `letter-spacing: .09em` | label-xs, gemischte Schreibweise, `letter-spacing: .04em` |\n| Position | Im Lesefluss, vor dem Titel, bringt einen ruhigen visuellen Anker | Inline mit Listen-/Tabellenelementen oder neben Karten-Titeln |\n| Wahl-Indiz | „Was für eine Art Inhalt ist das?“, kategorisch und thematisch | „In welchem Zustand, welchem Bereich ist dieses Element?“, punktuell |\n\nDer spürbare Unterschied entsteht durch Großschreibung und Letter-Spacing: Die Pill wirkt feierlicher, die Badge alltäglicher. Faustregel: Steht unter der Markierung ein Titel oder Beitrag, den sie einordnet, dann Pill. Sitzt die Markierung neben einem Element wie Modulname oder Tabellenzelle, dann Badge. Status (Live, Beta, Deprecated, Draft) ist immer Badge, die Pill hat keine Status-Variante.\n\n**Die Eyebrow-Form ist voll belegt, keine fünfte Bedeutung darauf.** `--ty-label-xs` plus `uppercase`, `letter-spacing:.09em` und `--co-ink` ist pixelgleich in vier Rollen im Einsatz: `.ep-hero-eyebrow` (Haltung, „Verbunden gedacht“), `.ep-section-label` (Sektions-Thema, „Was wir tun“), `.ep-card-eyebrow` (Brand Area, in Bereichsfarbe) und `.stoerer-topic` (Inhaltstyp, „Nächste Veranstaltung“). Aufgelöst wird das nur durch die Position (in einer Kachel, über einer Sektion), nicht durch die Form. Eine neue Komponente, die eine fünfte Bedeutung auf dieselbe Form legt, macht die kleinste Label-Ebene beliebig. Wer eine fünfte Rolle braucht, gibt ihr ein eigenes Unterscheidungsmerkmal (Glyph vor dem Label, neutrale statt farbiger Schrift) oder benutzt eine vorhandene Rolle.\n\n## Barrierefreiheit\n\n| Aspekt | Regel |\n|---|---|\n| Interaktivität | Klickbare Chips als `<button>` oder `<a>` implementieren, nie als `<div>` mit `onClick`, nur native Elemente sind per Tastatur und Screenreader korrekt bedienbar |\n| Auswahl-Zustand | Toggle-Chips mit `aria-pressed=\"true/false\"` versehen, der visuelle Auswahl-Stil allein ist für Screenreader nicht erkennbar |\n| Kontrast | Chip-, Badge- und Pill-Text auf dem jeweiligen Hintergrund mindestens 4,5 zu 1 prüfen, besonders bei markierten oder aktiven Chips, da sich Hintergrund und Textfarbe ändern |\n| Pill-`aria-label` | Pflicht bei Pills mit Trennzeichen (`·`), Screenreader verschlucken das Punktzeichen oder lesen es als „Punkt“. `aria-label` mit ausgeschriebener Form (z. B. „Bereich Effektive Software, Format Meet-Up“) schreibt die Aussprache vor. Bei Pills ohne Trennzeichen kann das `aria-label` entfallen, der sichtbare Text reicht |\n\n## Dos & Don'ts\n\n**Tun**\n\n- Chips für aktive Filterauswahl, die Nutzerin steuert selbst, was sichtbar ist.\n- Badge für reinen Status, „Live“, „Beta“, „Deprecated“, „Draft“, immer ohne Klick-Handler.\n- Pill als redaktionellen Eyebrow vor einem Titel einsetzen, ordnet den Beitrag thematisch ein und stiftet Erwartung.\n- Bereichsfarbe konsequent einhalten, aktiver Chip einer Brand Area nutzt nur deren Farbtoken.\n- Chip-Labels auf max. 3 Wörter begrenzen, klare, scanbare Begriffe.\n- Semantische Statusfarben bei Badges einhalten, Grün für positiv, Gelb für Warnung, Rot für Fehler.\n- Pills mit Trennzeichen (`·`) immer mit einem `aria-label` in ausgeschriebener Form versehen.\n\n**Nicht tun**\n\n- Chips rein dekorativ einsetzen ohne Toggle-Funktion, verwirrt Nutzerinnen über Interaktivität.\n- Badges klickbar machen ohne visuelles Feedback, Badges sind keine Buttons.\n- Pills für Status verwenden, dafür gibt es Status-Badges, Pill ist redaktioneller Eyebrow, nicht Statusmarker.\n- Bereichsfarben im selben Kontext mischen, `data-area=\"co\"` und `data-area=\"ki\"` Chips nicht gemeinsam in einer Filterleiste.\n- Chip-Labels als vollständige Sätze, zu lang, passt nicht zur kompakten Form.\n- Mehr als 7 Chips in einer Gruppe, lieber kategorisieren oder eine Auswahlliste verwenden.\n- Mehrere Pills nebeneinander an demselben Inhalt, eine Pill pro Beitrag reicht.\n\n## Verwandte Seiten\n\n- Chip (`Komponenten/Chips, Badges & Pills/Chip`)\n- Status-Badge (`Komponenten/Chips, Badges & Pills/Status-Badge`)\n- Bereichs-Badge (`Komponenten/Chips, Badges & Pills/Bereichs-Badge`)\n- Pill (`Komponenten/Chips, Badges & Pills/Pill`)\n",
13
+ "summary": "# Verwendung Drei Formen, die leicht verwechselt werden: interaktive Filter-Chips (Toggle)..."
14
+ }
15
+ }
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "components": {
3
+ "komponenten-code-block-code-block": {
4
+ "id": "komponenten-code-block-code-block",
5
+ "name": "komponenten-code-block-code-block",
6
+ "docs": {
7
+ "komponenten-code-block-code-block--verwendung": {
8
+ "id": "komponenten-code-block-code-block--verwendung",
9
+ "name": "Verwendung",
10
+ "path": "./src/docs/komponenten/code-block-verwendung.mdx",
11
+ "title": "Komponenten/Code-Block/Code-Block",
12
+ "content": "import { Meta } from '@storybook/addon-docs/blocks';\nimport * as CodeBlockStories from '../../lib/code-block/code-block.stories';\n\n<Meta of={CodeBlockStories} name=\"Verwendung\" />\n\n# Verwendung\n\nCode-Block stellt Quellcode und Terminal-Ausgaben für Wissens- und Technikbeiträge dar:\nStandard, mit Zeilennummern, Terminal und Inline-Code. Alle vier sind Varianten\nderselben Story `Komponenten/Code-Block/Code-Block` (gesteuert über die Property `terminal` sowie\ndie Klassen `.cb-numbered` und `.cb-prose`), kein eigenes Bauteil je Variante.\n\n## Wann welche Variante\n\n| Variante | Wann einsetzen | Nicht geeignet für |\n|---|---|---|\n| Standard | Einzelne Funktionen, Konfigurationen, kurze Snippets | Shell-Befehle, langen Fließtext-Kontext |\n| Mit Zeilennummern | Wenn im Begleittext auf bestimmte Zeilen verwiesen wird (ab etwa 10 Zeilen) | Snippets unter 5 Zeilen, Terminal-Ausgaben |\n| Terminal | Installations- und Setup-Befehle, CLI-Workflows | Programm-Code mit Syntax-Highlighting |\n| Inline-Code | Bezeichner, Tokens, Klassen im Fließtext, maximal eine Zeile | Mehrzeilige Snippets, vollständige Statements |\n\n## Barrierefreiheit\n\n| Aspekt | Regel |\n|---|---|\n| Semantik | `<pre><code>` als Grundgerüst, Screenreader kündigen den Inhalt als „Code“ an; `<pre>` allein reicht nicht |\n| Scrollbarkeit | Scrollbare `<pre>`-Blöcke mit `tabindex=\"0\"` versehen, sonst per Tastatur nicht erreichbar |\n| Copy-Button | `aria-label=\"Code kopieren\"` auf dem Copy-Button, der Text „Kopieren“ allein nennt kein Ziel. Das Label bleibt nach dem Kopieren **stabil** — ein Bedienelement trägt den Namen seiner Funktion, nicht den seines letzten Ereignisses. Die Erfolgsmeldung läuft stattdessen über eine eigene, von Anfang an im DOM stehende Live-Region (`role=\"status\"`, `.sr-only`) neben dem Button; eine Region, die erst beim Klick entsteht, kündigt bei vielen Screenreadern nichts an |\n\n## Dos & Don'ts\n\n**Tun**\n\n- Sprache beziehungsweise Programmiersprache immer sichtbar im Header angeben, erleichtert das Lesen und ermöglicht Syntax-Highlighting\n- Zeilennummern nur ab etwa 10 Zeilen beziehungsweise wenn der Begleittext explizit auf Zeilen verweist (zum Beispiel „Zeile 8 setzt den Radius“)\n- Terminal-Variante für alle Shell-Befehle und Ausgaben, der grüne Prompt signalisiert sofort: hier wird etwas ausgeführt\n- Copy-Button immer einbauen mit `aria-label`, Leser kopieren Code häufiger als sie ihn abtippen\n- `<pre><code>` als semantisches Grundgerüst verwenden\n- Code-Block immer mit einer kurzen Einleitung im Fließtext versehen, Kontext vor dem Block, nicht danach\n\n**Nicht tun**\n\n- Inline-Code für mehrzeilige Snippets oder Beispiele, ab 2 Zeilen gehört der Inhalt in einen Block\n- Screenshots von Code statt echtem Text, nicht kopierbar und nicht zugänglich für Screenreader\n- Highlight-Klassen (`.k`, `.s` …) für rein dekorative Farben zweckentfremden, sie tragen Bedeutung und sollten semantisch korrekt eingesetzt werden\n- Terminal und Code-Block mischen, Shell-Befehle und Programm-Code gehören in getrennte Blöcke\n- Eigene Hintergrundfarben außerhalb der Token setzen, bricht Konsistenz und Dark-Mode-Kompatibilität\n- Code ohne Sprachkennzeichnung ausliefern, Syntax-Highlighting und Screenreader-Kontext fehlen dann\n- Zu lange Zeilen ohne Umbruch oder Block-Code direkt im Fließtext ohne visuellen Abstand\n\n## Verwandte Seiten\n\n- Komponenten/Code-Block/Code-Block (Story, alle vier Varianten)\n",
13
+ "summary": "# Verwendung Code-Block stellt Quellcode und Terminal-Ausgaben für Wissens- und Technikbei..."
14
+ }
15
+ }
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "components": {
3
+ "komponenten-dropdowns-custom-select": {
4
+ "id": "komponenten-dropdowns-custom-select",
5
+ "name": "komponenten-dropdowns-custom-select",
6
+ "docs": {
7
+ "komponenten-dropdowns-custom-select--verwendung": {
8
+ "id": "komponenten-dropdowns-custom-select--verwendung",
9
+ "name": "Verwendung",
10
+ "path": "./src/docs/komponenten/dropdowns-verwendung.mdx",
11
+ "title": "Komponenten/Dropdowns/Custom Select",
12
+ "content": "import { Meta } from '@storybook/addon-docs/blocks';\nimport * as SelectStories from '../../lib/select/select.stories';\n\n<Meta of={SelectStories} name=\"Verwendung\" />\n\n# Verwendung\n\nAuswahl-Komponenten über das native `<select>` hinaus: Custom Select (gestylte Einzelauswahl mit Listbox-Popup, Häkchen und Bereichs-Akzent) und Combobox (Tipp-Filter über langen Listen, optional Multi-Select mit Chips). Beide sind markup-getrieben, semantisches HTML genügt, das Skript ergänzt IDs, ARIA-Zustände und Tastatur. Durchgängig WCAG AA: `role=\"listbox\"`/`role=\"option\"`, `aria-expanded`, `aria-activedescendant`, Fokusring in beiden Modi.\n\n## Wann welche Variante\n\n| Form | Wann einsetzen |\n|---|---|\n| Natives `<select>` | Standard für kurze Listen (bis 8 Optionen) in Formularen, siehe Inputs & Forms |\n| Custom Select | Wenn die Auswahl optisch zum Bereich gehören soll (Häkchen, Tönung) oder das native Popup nicht ausreicht |\n| Combobox | Ab ca. 10 Optionen, wo Tippen schneller ist als Scrollen |\n| Multi-Select | Nur für echte Mehrfachauswahl (Tags, Filter). Kein eigenes Bauteil: Multi-Select ist der Modifier `.is-multi` auf der Combobox, keine separate Komponente |\n\n| Baustein | Zweck |\n|---|---|\n| `.ep-select` | Gestylte Einzelauswahl, `data-area` setzt den Bereichs-Akzent, `data-name` erzeugt ein verstecktes Feld für den Submit |\n| `.ep-combobox` | Tipp-Filter über langen Listen, `data-empty-text` setzt den Leerzustand |\n| `.is-multi` | Modifier am `.ep-combobox` für Mehrfachauswahl mit Chips |\n| `.ep-combobox-clear` | Lösch-Button (per Skript injiziert), erscheint bei Texteingabe und leert das Feld, Multi behält die Chips |\n| `data-value` | Maschinenwert je Option, sichtbarer Text ist das Label (oder `data-label`) |\n| `ep:change` | Custom-Ereignis bei Auswahl (`detail.value`, `detail.selected`) zum Anbinden eigener Logik |\n\nDas Popup schwebt über fremdem Inhalt und kann sich dort nicht auf den Schatten verlassen, der Rand übernimmt deshalb im Dark Mode `--bd-strong-c` statt `--bd-c`. Gilt für jedes schwebende Panel, auch das Topnav-Untermenü und das Suchpanel.\n\n## Barrierefreiheit\n\n| Attribut / Verhalten | Zweck |\n|---|---|\n| `role=\"listbox\"` / `option` | Popup und Einträge als Auswahlliste ausgewiesen, vom Skript gesetzt beziehungsweise ergänzt |\n| `aria-expanded` | Offen-Zustand am Trigger beziehungsweise am Combobox-Input |\n| `aria-activedescendant` | Tastaturcursor ohne Fokusverlust, der hervorgehobene Eintrag wird vorgelesen |\n| Tastatur | Pfeiltasten, Pos1/Ende, Type-ahead (Select), Enter wählt, Esc/Tab schließt |\n| Fokusring | Neutral-Teal `--focus-ring` in beiden Modi, mindestens 3 zu 1 Rahmenkontrast |\n\nBeim Schließen ohne Auswahl bleibt kein loser Filtertext stehen: die Einzelauswahl fällt auf das Label der gewählten Option zurück (beziehungsweise leert), getippter Text bleibt nie im Feld hängen.\n\n## Dos & Don'ts\n\n**Tun**\n\n- Natives `<select>` für kurze Listen behalten, Custom Select erst, wenn Bereichs-Akzent oder Popup-Styling gebraucht wird.\n- Combobox ab ca. 10 Optionen einsetzen, wo Tippen schneller filtert als Scrollen.\n- Jedes Dropdown mit sichtbarem `.ep-select-label` versehen, Placeholder ist kein Label.\n- Den Bereichs-Akzent zur Brand Area der Seite passend wählen (Corporate, Angewandte KI, Effektive Software, Wirksame Organisationen).\n\n**Nicht tun**\n\n- Custom Select für eine Ja/Nein- oder Zwei-Optionen-Wahl, dafür sind Radios oder ein nativer Select besser.\n- Den Fokusrahmen pro Bereich einfärben, der Akzent gehört in den gewählten Eintrag, nicht in den Fokus.\n- Multi-Select als Notlösung für eine Einzelauswahl, das verwirrt die Erwartung an die Eingabe.\n- Mehrere Bereichs-Akzente in einem Formular mischen, ein Formular gehört zu genau einer Brand Area.\n\n## Verwandte Seiten\n\n- Custom Select (`Komponenten/Dropdowns/Custom Select`)\n- Combobox (`Komponenten/Dropdowns/Combobox`), Multi-Select ist der Modus `.is-multi` derselben Story\n",
13
+ "summary": "# Verwendung Auswahl-Komponenten über das native `` hinaus: Custom Select (gestylte Einzel..."
14
+ }
15
+ }
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "components": {
3
+ "komponenten-feedback-snackbar": {
4
+ "id": "komponenten-feedback-snackbar",
5
+ "name": "komponenten-feedback-snackbar",
6
+ "docs": {
7
+ "komponenten-feedback-snackbar--verwendung": {
8
+ "id": "komponenten-feedback-snackbar--verwendung",
9
+ "name": "Verwendung",
10
+ "path": "./src/docs/komponenten/feedback-verwendung.mdx",
11
+ "title": "Komponenten/Feedback/Snackbar",
12
+ "content": "import { Meta } from '@storybook/addon-docs/blocks';\nimport * as SnackbarStories from '../../lib/snackbar/snackbar.stories';\n\n<Meta of={SnackbarStories} name=\"Verwendung\" />\n\n# Verwendung\n\nDrei Snackbar-Varianten (Default, OK, Fehler) für Kontaktformular, Newsletter-Anmeldung und Validierungsfehler auf Landingpages.\n\n## Wann welche Variante\n\n| Variante | Wann einsetzen | ARIA |\n|---|---|---|\n| Default | Neutrale Statusmeldung mit optionaler Aktion, Formular zwischengespeichert, Cookie-Einwilligung bestätigt | `role=\"status\"` `aria-live=\"polite\"` |\n| OK / Erfolg | Abgeschlossene Landingpage-Aktion, Kontaktformular gesendet, Newsletter-Anmeldung bestätigt | `role=\"status\"` `aria-live=\"polite\"` |\n| Fehler | Versand- oder Serverfehler nach Submit, bleibt sichtbar bis die Nutzerin aktiv reagiert | `role=\"alert\"` `aria-live=\"assertive\"` |\n\n| Eigenschaft | Regel |\n|---|---|\n| Auto-Dismiss | Default und OK nach 4 bis 6 Sekunden, Fehler-Snackbar bleibt offen |\n| Position | Unten links oder zentriert, nie über primären Aktionsschaltflächen |\n| Menge | Max. 1 Snackbar gleichzeitig, neue verdrängt die vorherige |\n| Aktion | Max. 1 Aktion pro Snackbar, kurzes Verb, max. 2 Wörter |\n\nFeldfehler gehören immer inline unter das betroffene Feld, nicht als Snackbar: die Snackbar transportiert nur den globalen Submit-Fehler, sonst verliert die Nutzerin den Bezug zum Feld.\n\n## Barrierefreiheit\n\n| Aspekt | Regel |\n|---|---|\n| Default & OK | `role=\"status\"`, `aria-live=\"polite\"` |\n| Fehler | `role=\"alert\"`, `aria-live=\"assertive\"`, bleibt offen bis aktive Reaktion |\n| Dark Mode | Die Snackbar schwebt über beliebigem Inhalt und kommt im Light ohne Rahmen aus. Im Dark liegt sie sehr nah am Grund, deshalb bekommt sie dort zusätzlich `--bd-strong-c` als Rand, dieselbe Regel wie bei Menüs und Popover |\n| Status-Flächen | Farbe aus `--c-success-bg` und `--c-error-bg`, nicht aus eigenen Hexwerten, sonst bleiben sie bei einer Token-Änderung zurück |\n\n## Dos & Don'ts\n\n**Tun**\n\n- OK-Snackbar nach erfolgreichem Formularversand, die Nutzerin bekommt klare Rückmeldung ohne Seitenwechsel.\n- Feldfehler inline direkt unter dem Eingabefeld zeigen, Snackbar nur für globale Submit-Fehler.\n- Fehler-Snackbar offen lassen, bis die Nutzerin aktiv reagiert, kein Auto-Dismiss bei kritischen Meldungen.\n- Aktions-Label als konkretes Verb, „Erneut versuchen“, „Jetzt senden“, „Schließen“.\n- `role=\"alert\"` nur für Versand- oder Serverfehler, nicht für Bestätigungen und Hinweise.\n\n**Nicht tun**\n\n- Feldfehler als Snackbar, die Nutzerin verliert den Bezug zum betroffenen Feld.\n- OK-Snackbar auto-dismissen, bevor die Nutzerin sie gelesen hat, bei langen Erfolgstexten den Timer auf 6 Sekunden setzen.\n- Snackbar für nächste Schritte nutzen, nach Formularversand lieber per E-Mail oder Folgeseite bestätigen.\n- Mehrere Snackbars gleichzeitig anzeigen, eine neue Meldung verdrängt immer die vorherige.\n- Fehler-Snackbar als einzige Validierung verwenden, Pflichtfelder müssen schon vor dem Submit markiert sein.\n\n## Verwandte Seiten\n\n- Snackbar (`Komponenten/Feedback/Snackbar`)\n",
13
+ "summary": "# Verwendung Drei Snackbar-Varianten (Default, OK, Fehler) für Kontaktformular, Newsletter..."
14
+ }
15
+ }
16
+ }
17
+ }
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
+ }