@conciso/design-system 2.3.0 → 2.4.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 (3) hide show
  1. package/README.md +2 -2
  2. package/package.json +5 -3
  3. package/CHANGELOG.md +0 -1786
package/CHANGELOG.md DELETED
@@ -1,1786 +0,0 @@
1
- # Changelog
2
-
3
- Alle nennenswerten Änderungen am Conciso Design System. Format nach
4
- [Keep a Changelog](https://keepachangelog.com/de/1.1.0/), Versionierung nach
5
- [SemVer](https://semver.org/lang/de/).
6
-
7
- Versionspolitik:
8
- - **MAJOR** — Breaking Changes an der öffentlichen API (umbenannte/entfernte
9
- Klassen oder Tokens, geänderte Ladereihenfolge).
10
- - **MINOR** — neue Komponenten/Tokens, abwärtskompatibel.
11
- - **PATCH** — Bugfixes, Kontrast-/Dark-Mode-Korrekturen, Doku.
12
-
13
- Releases werden als Git-Tags `vX.Y.Z` markiert.
14
-
15
- > **Eingefroren ab Version 2.0.0.** Ab hier entsteht ein Release automatisch aus
16
- > Conventional Commits (siehe
17
- > [ADR-0010](docs/adr/0010-release-ausloesung-und-versionsquelle.md)); die Notizen dazu
18
- > stehen im jeweiligen GitHub-Release, nicht mehr in dieser Datei. Die
19
- > Abschnitte unterhalb dieser Zeile sind der letzte handgeschriebene Stand.
20
-
21
- ## [2.0.0] - 2026-09-23
22
-
23
- ### Breaking
24
- - **`@conciso/design-system-angular` verlangt jetzt Angular 22.** Die peerDependencies
25
- `@angular/common`, `@angular/core`, `@angular/forms` und `@angular/platform-browser`
26
- stehen auf `^22.0.0` statt `^21.2.0`; die Lib wird mit dem Angular-22-Compiler gebaut.
27
- Konsumenten auf Angular 21 bleiben auf 1.x, bis sie selbst auf 22 gehen. Mit Angular 22
28
- kommt TypeScript 6.0 als Pflicht für den Build des Konsumenten (`>=6.0 <6.1`). Die
29
- mitgelieferten Icons (`@ng-icons/core`, `@ng-icons/heroicons`) springen von 33 auf 36;
30
- der Konsument fasst sie nicht direkt an (siehe ADR-0004), bekommt sie aber als
31
- `dependency` mit. Die Consumer-Fixture baut gegen Angular 22.
32
- - **Inhalts-Inputs von 16 Komponenten sind jetzt `input.required()`.** Betroffen:
33
- `BlockquoteComponent` (`quote`, `name`), `TestimonialComponent` (`quote`, `name`),
34
- `TeamVoiceComponent` (`quote`, `name`), `StatCardComponent` (`value`, `label`),
35
- `StatStripComponent` (`stats`), `DownloadCtaComponent` (`title`, `primaryLabel`),
36
- `FaqComponent` (`items`), `CarouselComponent` (`slides`), `LogoCarouselComponent`
37
- (`sets`), `CardComponent` (`title`, `text`), `AreaBadgeComponent` (`label`),
38
- `StatusBadgeComponent` (`label`), `ChipComponent` (`label`), `PillComponent`
39
- (`label`), `CodeBlockComponent` (`code`) und `SnackbarComponent` (`message`). Diese
40
- Inputs trugen bisher erfundenen Conciso-Beispieltext als Default (Namen, Zitate,
41
- Kennzahlen, FAQ-Einträge, Slide-Inhalte …) — ein vergessenes Binding lieferte damit
42
- unbemerkt Marketingtext statt eines auffälligen Fehlers aus. Fehlt der Wert jetzt,
43
- wirft Angular zur Laufzeit `NG0950`; ein vergessenes Binding fällt damit sofort auf,
44
- statt still Demo-Inhalt auszuliefern. Reines Beiwerk (`roleLabel`, `trendText`,
45
- `eyebrow`/`desc`/`meta`/`secondaryLabel` bei DownloadCta, `actionLabel` bei Card und
46
- Snackbar) bleibt optional, defaultet aber jetzt auf `''` statt auf erfundenen Text.
47
- Die bisherigen Default-Texte sind nicht verloren — sie stehen jetzt als explizite
48
- `args` in den jeweiligen Stories (`storybook-angular/src/lib/**/*.stories.ts`) und
49
- rendern dort weiterhin unverändert.
50
- - **`FieldShellComponent` ist kein Export der öffentlichen API mehr.** Die Komponente war nie als
51
- öffentliche API gedacht — ihre Klassendoku bezeichnet sie seit jeher als intern
52
- („Präsentations-Hülle für alle Field-Komponenten“), es gab weder eine eigene Story noch JSDoc
53
- dafür. Textfeld, Textbereich und Auswahlfeld beziehen sie weiterhin über den relativen Import
54
- (`./field-shell.component`), nur der Re-Export aus `public-api.ts` entfällt. Wer eigene Feldtypen
55
- baut, setzt die Klassen der CSS-Schicht (`.field`, `.helper`, `.error-msg`, …) direkt zusammen,
56
- statt sich an eine interne Hilfskomponente zu binden.
57
- - **97 interne Member sind von `public` auf `protected` gewechselt, darunter 35 Methoden.**
58
- Betroffen sind Template-Handler und internes Zustandsgerüst, etwa `ChipComponent.toggle`,
59
- `SelectComponent.open`/`close`/`select`, `CarouselComponent.next`/`goTo` und die
60
- `handleInput`/`handleBlur` der Feld-Basis. Sie waren nie als API gedacht, standen aber in der
61
- generierten TypeScript-Oberfläche und seit dem Docgen-Server aus
62
- [ADR-0006](docs/adr/0006-storybook-10-6-docgen-server-mcp-und-theming.md) auch in Props-Tabelle
63
- und Manifest. Wer eine dieser Methoden von außen aufgerufen hat, kompiliert nicht mehr; der
64
- vorgesehene Weg führt über Inputs und Outputs. Die Regel dahinter steht in
65
- [ADR-0007](docs/adr/0007-api-konventionen-der-angular-komponenten.md): Interfaces wie
66
- `ControlValueAccessor` bleiben notwendigerweise `public` und tragen nur `@internal`.
67
-
68
- ### Added
69
- - **`SectionComponent` (`[cdsSection]`), erste Angular-Wrapper-Komponente für die
70
- Seitenbausteine der Beispielseiten — und wie `cds-icon-card` (Ticket 05) auf
71
- Attributselektor umgestellt.** Wrapper um `.ep-section` samt dem optionalen
72
- Kopf-Trio `.ep-section-label`/`-h2`/`-sub` (`label`, `heading`, `sub`, alle
73
- Beiwerk), dazu `area` (`.t-{area}` auf dem Label) und `labelledBy`. Erste Fassung
74
- war `cds-section` als eigenes Element, das selbst zwischen `<section
75
- aria-labelledby="…">` (mit zugänglichem Namen) und einem namenlosen `<div>` wählte.
76
- Eine Sektionsfläche musste dabei auf einem UMSCHLIESSENDEN Element sitzen, nicht auf
77
- dem `<cds-section>`-Host — gemessen im laufenden Storybook (Details in
78
- `docs/adr/0008-selektortyp-der-wrapper-komponenten.md`, „Fall 2“): Host-`background`
79
- wurde von `getComputedStyle` und `getBoundingClientRect()` beide als korrekt gemeldet,
80
- der Screenshot zeigte trotzdem keinen einzigen gemalten Pixel — ein unbekanntes
81
- Custom Element ohne eigenen Inline-Inhalt, dessen Block-Kind herausgebrochen wird,
82
- hat keine eigene Box zu füllen. Jetzt `section[cdsSection], div[cdsSection]`: der
83
- Konsument wählt das Tag (und damit, ob überhaupt eine `<section>`-Landmark möglich
84
- ist), die Komponente setzt nur noch `aria-labelledby`, wenn ein Name verfügbar ist.
85
- Die Fläche sitzt jetzt direkt am Host (`<section cdsSection
86
- style="background:…">`), kein umschließendes Element mehr nötig — bewusst weiterhin
87
- **kein** `background`-Input, die Sektionsfläche bleibt eine Entscheidung der Seite,
88
- nicht des Bauteils. `@if`/`@else`, `<ng-template>` und `NgTemplateOutlet` entfallen,
89
- das Template ist jetzt linear. Neue Story `Fläche am Host`, `Interaktiv`/`Ohne
90
- Kopf`/`Benannt von außen`/`Nur Überschrift`/`Bereichsgefärbtes Label` auf
91
- `<section cdsSection>`/`<div cdsSection>` umgestellt. Doku-Sektion „Sektion“
92
- (`docs/index.html#sec-section`) entsprechend nachgezogen. Erstes Ticket der
93
- Seitenbausteine-Serie (`.scratch/angular-seitenbausteine/spec.md`); die
94
- Kopf-Trio-Struktur trägt 133 der Beispielseiten-Sektionen, 85 davon mit Kopfzeile.
95
- - **`HeroImageComponent` (`cds-hero-image`), zweites Ticket der Seitenbausteine-Serie.**
96
- Wrapper um `.hero-image` (`css/components.css:851–878`) samt optionaler Caption als
97
- Gradient-Overlay (`-caption`, `-caption-eyebrow`, `-caption-title`, `-caption-text`):
98
- vollbreites, randloses `<figure>` im 21:9-Format, Standard-Hero auf allen
99
- Customer-Pages (17 Vorkommen in den Beispielseiten). `src`/`alt` sind
100
- `input.required()`, `eyebrow`/`heading`/`text` Beiwerk. Bleiben alle drei Textteile
101
- leer, entfällt das `<figcaption>` vollständig statt leer zu rendern. Neuer Input
102
- `headingLevel` (`1` Default, `2`) macht die Heading-Ebene des Caption-Titels
103
- explizit, für Beitrags-Heros, die unter einem eigenen `<h1>` (Article-Header)
104
- sitzen und sonst eine zweite Top-Überschrift bekämen. `objectPosition` als
105
- Style-Binding direkt am `<img>` (nicht als Klasse, die CSS-Schicht hat dafür keinen
106
- Modifier) — die eine bewusste Ausnahme von der Regel, dass Inline-Styles aus dem
107
- Mockup nicht in die Komponente wandern, weil der Bildausschnitt eine Eigenschaft
108
- des konkreten Bildes ist, nicht der Seite. `eager` (Default `true`) steuert
109
- `loading="eager"`/`"lazy"`. Bewusst **kein** `tabindex`/`id`-Handling: Das
110
- Sprungziel des Skip-Links ist eine Entscheidung der Seite, der Konsument setzt
111
- beides am `<cds-hero-image>`-Host. Neue Bauteil-Ebene `Komponenten/Hero/Hero-Bild`
112
- unter der bestehenden Doku-Sektion „Hero“ (bisher nur MDX-Übersicht) mit vier
113
- Stories (`Interaktiv`, `Ohne Caption`, `Beitrags-Hero (h2)`, `Bildausschnitt`);
114
- Icon `ui-computer-desktop` in `SECTION_ICON_KEYS` von der bisher verschmolzenen
115
- Ein-Kind-ID (`komponenten-hero--übersicht`) auf die jetzt eigenständige
116
- Sektions-ID (`komponenten-hero`) umgehängt.
117
- - **`StoererComponent` (`cds-stoerer`) und `StoererSetComponent` (`cds-stoerer-set`),
118
- drittes Ticket der Seitenbausteine-Serie.** Wrapper um `.stoerer`/`-set`/`-list`
119
- (`css/components.css:926–974`): das Verweiskachel-Set, das ausschließlich auf der
120
- Startseite oben rechts über dem Hero-Bild liegt. `cds-stoerer` ist eine vollständig
121
- klickbare `<a class="stoerer">` mit Pflicht-Inputs `topic`/`title`/`href` und
122
- Beiwerk `meta`/`date` (ISO, formatiert als deutsches Langdatum); bleiben `meta` und
123
- `date` beide leer, entfällt die Meta-Zeile vollständig statt leer zu rendern. Der
124
- `sr-only`-Trenner zwischen Datum und Ort/Lesezeit bleibt erhalten, wenn beide
125
- gesetzt sind. Das Icon ist wie bei `cds-icon-card` (Ticket 05) projizierter Inhalt
126
- (`<ng-content select="[cdsIcon]">`, nicht ein `icon`-Input), weil die
127
- Störer-Icons wechselnde Heroicons sind, keine DS-Bereichsglyphen; abweichend von
128
- Ticket 05 trägt das projizierte `<svg>` die Klasse `stoerer-icon` selbst, weil
129
- `.stoerer-icon` (anders als `.ep-card-icon`) direkt auf dem SVG sitzt, ohne
130
- Container, der die Größe per Nachfahren-Selektor durchreichen könnte. **Die
131
- `<li>`-Frage:** `.stoerer-list` ist ein `<ul>`, seine Kacheln müssen echte `<li>`
132
- sein, sonst bekommen sie laut HTML-AAM keine `listitem`-Rolle. Ein `display:
133
- contents`-Wrapper hätte neues CSS gebraucht (ADR-0001 verbietet das); gelöst über
134
- dasselbe `TemplateRef`/`ngTemplateOutlet`-Muster, das `cds-area-tabs`/`cds-area-tab`
135
- bereits etabliert: `cds-stoerer` rendert in ein internes `<ng-template>`,
136
- `cds-stoerer-set` liest die projizierten Kacheln per `contentChildren()` und setzt
137
- ihr Markup direkt in ein selbst gerendertes `<li>` — im laufenden Storybook geprüft,
138
- `<cds-stoerer>` taucht im gerenderten DOM nirgends auf, `<ul class="stoerer-list">`
139
- hat ausschließlich `<li>` als direkte Kinder. Der Positionsrahmen `.stoerer-hero`
140
- (umschließt Hero-Bild UND Set) bleibt bewusst beim Konsumenten. Neue Bauteil-Ebene
141
- `Komponenten/Hero/Störer` (Stories `Interaktiv`, `Zwei Kacheln über dem Hero`,
142
- `Ohne Meta`), `storySort.order` der Sektion „Hero“ um `Störer` ergänzt.
143
- - **`LinkCardComponent` (`cds-link-card`) und `FeaturedCardComponent`
144
- (`cds-featured-card`), viertes Ticket der Seitenbausteine-Serie: die beiden
145
- klickbaren Kartenvarianten, die `CardComponent` laut eigener Klassendoku bewusst
146
- nicht abdeckt.** `cds-link-card` ist ein echtes `<a class="card card-elevated">`
147
- (css/components.css:147–154) — die ganze Fläche ist der Link und trägt deshalb
148
- nach `CONTRIBUTING.md` §4 den Schatten, den `cds-card` nicht trägt; Struktur wie
149
- `cds-card` (optionale bereichsgefärbte `.card-media`, `.card-eyebrow`/`.card-title`/
150
- `.card-text`), dazu ein optionaler Fuß `.card-cta-link` (`ctaLabel`, leer = kein
151
- Fuß) mit `ctaPinned` (`--pinned`, unten angeheftet für gleich hohe Karten im
152
- Raster) — als `<span>`, nicht als zweites `<a>`, weil die Karte selbst schon `<a>`
153
- ist. `cds-featured-card` bildet `.card-featured` ab (css/components.css:230–249):
154
- Bild links 60 %, Textspalte rechts 40 % als absolut positioniertes Overlay, mit
155
- `.card-title-hero` (Serif-Editorial-Titel) und optionaler `.pill`; mit gesetztem
156
- `href` ein `<a class="card card-elevated card-featured">`, sonst ein `<article
157
- class="card card-featured">` ohne `.card-elevated` (die Klasse wirkt laut §4
158
- ohnehin nur auf `a.card-elevated`, eine `<article>` bekäme den Schatten auch mit
159
- gesetzter Klasse nicht). Media und Body stehen dafür einmal in einem gemeinsamen
160
- `<ng-template>` und werden in beiden Zweigen (`<a>`/`<article>`) per
161
- `<ng-container [ngTemplateOutlet]>` eingesetzt — dasselbe Muster wie
162
- `SectionComponent`; `<ng-container>` rendert selbst kein Element, die
163
- Kindselektoren bleiben unberührt. **Die Pill ist direkt komponiertes Markup, kein
164
- `<cds-pill>`:** `.card-featured` arbeitet mit direkten Kindselektoren
165
- (`.card-featured>.card-media`, `.card-featured-body>.pill`/`-.card-text`/
166
- `-.card-title-hero`); `<cds-pill>` rendert sein `.pill`-Element eine Ebene unter
167
- dem eigenen Host, ein projiziertes `<cds-pill>` stünde also zwischen
168
- `.card-featured-body` und `.pill` und die Kindselektoren griffen still nicht mehr
169
- (Layout bricht, kein Fehler). Dieselbe Abwägung wie in `download-cta.component.ts`
170
- (Kommentar über den Buttons): Klassen direkt komponieren statt eine
171
- Wrapper-Komponente dazwischenzusetzen. Weil die Pille direkt komponiert wird,
172
- übernimmt `cds-featured-card` auch die `aria-label`-Regel von `PillComponent` von
173
- Hand: `pillAriaLabel` überschreibt den Default „Bereich `<pill>`“ für Fälle, in
174
- denen die Pille keinen Bereich benennt (z. B. „12 min Lesezeit“ →
175
- `aria-label="Lesezeit 12 Minuten"`). Im laufenden Storybook geprüft: die
176
- gerenderte Kette hat exakt die Kindselektoren, auf die das CSS zielt, kein
177
- zusätzliches Element dazwischen, kein `<cds-pill>`-Tag im DOM — erneut gemessen
178
- nach der Umstellung auf `ngTemplateOutlet`, unverändertes Ergebnis. Neue
179
- Bauteil-Ebenen `Komponenten/Cards & Teaser/Klickbare Karte` (Stories `Interaktiv`,
180
- `Pro Bereich`, `Im Raster`) und `Komponenten/Cards & Teaser/Featured-Karte`
181
- (Stories `Interaktiv`, `Pro Bereich`, `Pille als Lesezeit`, `Ohne Pill`,
182
- `Ohne Link`; bewusst **kein** Grid-Beispiel — die Doku verlangt „genau einer auf
183
- der Bühne“), `storySort.order` der Sektion „Cards & Teaser“ entsprechend ergänzt.
184
- - **`IconCardComponent` (`[cdsIconCard]`), fünftes Ticket der Seitenbausteine-Serie:
185
- die Icon-Teaser-Kachel der Beispielseiten (81 Vorkommen) — UND erste Komponente mit
186
- Attributselektor statt eigenem Element.** Bildet `.ep-card` ab
187
- (css/components.css:1298–1340): farbige Icon-Kachel, Eyebrow, Titel, Text, optionale
188
- Pfeil-CTA-Zeile. Erste Fassung war `cds-icon-card` als eigenes Element mit
189
- `href`-Input (`<a>`/`<div>` per `@if`/`@else`). Gemessen im laufenden Storybook: zwei
190
- Karten mit unterschiedlich langem Text ergaben im `.ep-cards`-Grid ungleiche
191
- `.ep-card`-Höhen (174px/270px), weil `align-items:stretch` nur den unsichtbaren
192
- `<cds-icon-card>`-Host streckt, nicht das `.ep-card`-Element eine Ebene darunter.
193
- Dieselbe Messung ohne Host-Wrapper (rohes Markup direkt in `.ep-cards`) ergab
194
- 306px/306px, CTA-Unterkanten auf identischer Y-Koordinate — die CSS-Schicht gleicht
195
- Kartenhöhen also korrekt aus, der Fehler saß im Wrapper-Element. Konsequenz, analog
196
- zu Angular Materials `a[mat-button], button[mat-button]`: Die Komponente ist jetzt
197
- `a[cdsIconCard], div[cdsIconCard]`, hängt sich also als Attribut an ein vom
198
- Konsumenten geschriebenes `<a>`/`<div>`, statt ein eigenes Host-Element zu sein.
199
- `href` ist damit kein Input mehr (natives Attribut am `<a>`). `.ep-card-link` hängt
200
- bewusst an „ist `<a>` UND hat `href`“, nicht am Tag allein: Ein `<a cdsIconCard>`
201
- ohne `href` ist weder fokussierbar noch hat es eine Link-Rolle, hätte mit dem Tag
202
- allein aber trotzdem den Link-Look (Schatten, Hover) bekommen — genau die Garantie,
203
- die die frühere `href`-Input-Fassung strukturell hatte (leerer String → zwingend der
204
- `<div>`-Zweig). Die Prüfung sitzt als **Methode im Host-Binding**
205
- (`'[class.ep-card-link]': 'isLink()'`), nicht als einmalig berechnetes Feld, weil
206
- `href` zur Laufzeit gesetzt werden kann: Host-Bindings werden bei jedem Refresh der
207
- ELTERN-Ansicht neu ausgewertet, unabhängig vom OnPush-Status der Komponente selbst
208
- — kein Sonderfall des in ADR-0007 §3 verworfenen Getter-Musters (das betraf
209
- Getter in der eigenen Kindvorlage). Empirisch mit einem Signal-getriebenen
210
- `[attr.href]`-Toggle zur Laufzeit geprüft (Spike, seither gelöscht): kein `effect()`
211
- nötig. Neue Story „Anker ohne Href“ pinnt den Fall. Erneut gemessen: alle Karten
212
- 306px, CTA-Unterkanten deckungsgleich — als Regressionsschutz jetzt eine
213
- Play-Funktion in der Story „Im Raster“ mit bewusst unterschiedlich langen Texten
214
- (vorher unauffällig gleich lang). `@angular-eslint/component-selector` erlaubt dafür
215
- jetzt zusätzlich zum Element-Standard (`kebab-case`, weiterhin für alle anderen
216
- Komponenten) einen `cds`-Attributselektor (`camelCase`) als benannte Ausnahme
217
- ([ADR-0008](docs/adr/0008-selektortyp-der-wrapper-komponenten.md)).
218
- Das Icon kommt als projizierter Inhalt (`<ng-content select="[cdsIcon]">`), anders
219
- als bei `cds-stoerer` aber OHNE dass das SVG eine eigene Größenklasse tragen muss:
220
- `.ep-card-icon` ist ein echter Container und reicht Maße/Stroke über den
221
- Nachfahren-Selektor `.ep-card-icon svg` durch. `data-area` sitzt auf Karte (Host),
222
- Icon-Kachel und Eyebrow, wie im Mockup; die CTA-Farbe braucht kein eigenes
223
- `data-area`, sie kommt über `.ep-card[data-area] .ep-card-cta`. **Kein
224
- `cdsIconCards`-Raster:** `.ep-cards` ist ein reines `display:grid` ohne Struktur
225
- oder Verhalten (dieselbe Begründung wie beim Verzicht auf einen
226
- `layout-grid`-Wrapper, siehe `spec.md`) — Konsumenten schreiben `<div
227
- class="ep-cards">` von Hand. Neue Bauteil-Ebene `Komponenten/Cards & Teaser/
228
- Icon-Karte` (Stories `Interaktiv`, `Als Link`, `Anker ohne Href`, `Pro Bereich`,
229
- `Im Raster`, `Ohne CTA`), `storySort.order` der Sektion „Cards & Teaser“
230
- entsprechend ergänzt.
231
- - **`FeatureComponent` (`[cdsFeature]`), sechstes Ticket der Seitenbausteine-Serie:
232
- die offene Feature-Zeile ohne Box und ohne Schatten (40 Vorkommen auf den
233
- Beispielseiten).** Bildet `.ep-feature` ab (css/components.css:1363–1379):
234
- Icon-Kachel links, Titel und Text rechts. Attributselektor von Anfang an
235
- (`div[cdsFeature]`), nicht erst nach einem Fehlversuch: das Mockup setzt die
236
- Spaltenklasse konsequent am selben Element wie `.ep-feature`
237
- (`class="ep-feature col-4"`), das Bauteil ist damit selbst Grid-Kind UND Ziel
238
- einer Layout-Klasse des Konsumenten — exakt das Kriterium aus
239
- [ADR-0008](docs/adr/0008-selektortyp-der-wrapper-komponenten.md), hier vorab
240
- angewandt statt nachträglich gemessen. Gemessen wurde trotzdem: die Story
241
- „Dreispalter“ setzt drei `.ep-feature` mit stark unterschiedlich langem Text als
242
- `col-4`-Geschwister in ein `.layout-grid` — alle drei 128px hoch, alle drei
243
- Unterkanten bei y=128, der Grid-Zeilenausgleich greift direkt auf `.ep-feature`,
244
- ohne Host-Element dazwischen. Das Icon kommt als projizierter Inhalt
245
- (`<ng-content select="[cdsIcon]">`); `.ep-feature-icon` ist wie `.ep-card-icon`
246
- ein Container, der Maße über den Nachfahren-Selektor `.ep-feature-icon svg`
247
- durchreicht, geht darin aber weiter als die Icon-Karte: es setzt zusätzlich
248
- `color` auf dem Container (`.ep-card-icon` tut das nicht), die vier
249
- `[data-area]`-Varianten liefern also nicht nur die Kachelfarbe, sondern auch den
250
- passenden Icon-Ton — ein projiziertes `<svg stroke="currentColor">` erbt ihn ohne
251
- eigenes Zutun, das Mockup nutzt genau das (`docs/index.html:10406` u. a.). Der
252
- optionale CTA (`.card-cta-link`), direktes Kind von `.ep-feature-body`, ist ein
253
- echter `<a href>`, wenn `ctaHref` gesetzt ist, sonst ein `<span>` mit identischer
254
- Optik — nie ein `<a>` ohne `href`. Ausgezählt in `docs/index.html`: alle 13
255
- `.card-cta-link`-Vorkommen in `.ep-feature-body` sind entweder ein `<a href="…">`
256
- (12×) oder ein `<span>` (1×, Platzhalter „Landingpage folgt“ ohne Ziel), niemals
257
- ein `<a>` ohne `href` — dieselbe Falle, die `cds-icon-card` über `isLink()`
258
- schließt, hier aber strukturell im `@if`/`@else`-Zweig statt am Host-Tag gelöst,
259
- weil der Host bei `cdsFeature` immer `<div>` ist. Ein `<a>` ohne `href` wäre weder
260
- fokussierbar noch hätte es eine Link-Rolle, sähe mit der `.card-cta-link`-Optik
261
- aber trotzdem bedienbar aus; erster Entwurf hatte das nur über `[attr.href]="…||
262
- null"` abgefedert, nicht ausgeschlossen. Zweite Korrektur an derselben Stelle:
263
- der neue `ctaAriaLabel`-Input. Erster Entwurf ging davon aus, das Mockup verzichte
264
- meist auf einen zusätzlichen `aria-label` — ausgezählt war das Gegenteil richtig:
265
- von den 12 `<a class="card-cta-link">` tragen 11 einen `aria-label`, weil sich nur
266
- 3 verschiedene sichtbare Texte auf diese 12 Links verteilen (`„Zur Landingpage“`
267
- allein zehnmal) und eine Screenreader-Linkliste sonst zehnmal denselben Namen
268
- hörte. Das Mockup disambiguiert durchgängig mit `<sichtbarer Text>: <Ziel>`
269
- (`aria-label="Zur Landingpage: KI Kickstart Workshops"`) — `ctaAriaLabel` bildet
270
- genau das nach, Default `''` (sichtbarer `ctaLabel`-Text bleibt zugänglicher
271
- Name), wirkt nur am `<a>`-Zweig. Neue Bauteil-Ebene
272
- `Komponenten/Cards & Teaser/Feature-Liste` (Stories `Interaktiv`, `Dreispalter`,
273
- `Mit CTA`, `Ohne Href`, `Pro Bereich`), `storySort.order` der Sektion
274
- „Cards & Teaser“ entsprechend ergänzt.
275
- - **`CtaBandComponent` (`[cdsCtaBand]`), siebtes Ticket der Seitenbausteine-Serie: das
276
- bereichsgefärbte Page-End-CTA-Band am Ende jeder Customer-Page.** Bildet
277
- `.ep-cta-band` samt Kopf-Duo `.ep-cta-h2`/`.ep-cta-sub` und einer Aktion ab
278
- (css/components.css:1456; 22 Vorkommen auf den Beispielseiten, ausnahmslos genau
279
- eine Aktion). Vier Entscheidungen, alle gemessen statt hergeleitet: **(1)**
280
- Attributselektor (`div[cdsCtaBand]`), derselbe „Fläche am Host“-Fall wie
281
- `cds-section` ([ADR-0008](docs/adr/0008-selektortyp-der-wrapper-komponenten.md)) —
282
- `.ep-cta-band` trägt kein `[data-area]` (per Grep geprüft), die Bandfläche kommt in
283
- allen 22 Vorkommen als Inline-Style direkt am Element; ein Element-Selektor hätte
284
- denselben ungemalten Hintergrund reproduziert, den ADR-0008 „Fall 2“ für
285
- `cds-section` maß. Nur `<div>`, kein `<section>`-Zwilling: keines der 22 Vorkommen
286
- variiert das Tag. **(2)** `area` färbt nur `.btn-{area}` auf der Aktion, nicht die
287
- Bandfläche — dieselbe Konsumenten-Zuständigkeit wie bei `cds-section`, hier
288
- zusätzlich erzwungen, weil das CSS an dieser Klasse ohnehin keinen
289
- `[data-area]`-Haken kennt. **(3)** Die `.btn-filled`+`.btn-on-band`-Klassen sind
290
- direkt komponiert, nicht über `cds-button` projiziert (Präzedenzfall
291
- `download-cta.component.ts`) — härterer Grund als dort: `cds-button` kennt zwar
292
- `.btn-on-band` (`variant="filled-on-band"`, geprüft über `npx storybook tools docs
293
- show --id komponenten-buttons-button`, Story „Auf Bereichs-Band“), rendert aber
294
- IMMER ein `<button>` und kann die geforderte `primaryHref`-gesetzt-→-`<a>`-
295
- Verzweigung strukturell nicht erfüllen. **(4)** Bewusst nur EINE Aktion, kein
296
- `secondaryLabel`. Ein erster Entwurf bot eine zweite Aktion an, ungeprüft gegen das
297
- Mockup — die Korrektur kam aus zwei Gegenproben: keines der 22 Vorkommen zeigt eine
298
- zweite Aktion, und `.btn-{area}.btn-on-band{color:…}`
299
- (css/components.css:47–51) überschreibt die Textfarbe JEDER Variante auf denselben
300
- Ton, den die Doku als Bandhintergrund nennt — nur `.btn-filled` tauscht zusätzlich
301
- die Füllung gegen Weiß, jede andere Variante wäre Text in Bandfarbe auf Bandfarbe.
302
- Eine zweite Aktion hätte also zwangsläufig dieselbe Optik wie die erste getragen,
303
- keine Hierarchie. Diese CSS-Lücke ist jetzt
304
- `.scratch/angular-seitenbausteine/issues/16-css-luecke-zweite-aktion-auf-band.md`;
305
- die Komponente trägt bis zu ihrer Klärung nur eine Aktion, ohne eigenes
306
- Flex-Layout dafür (das hätte eine Darstellung erfunden, die die CSS-Schicht unter
307
- diesem Namen nicht kennt — `display:flex;gap:var(--s3);flex-wrap:wrap` existiert
308
- bereits identisch als `.ep-hero-ctas`, css/components.css:1222, für einen anderen
309
- Bauteiltyp). Eine einzelne Aktion zentriert sich stattdessen über das ererbte
310
- `.ep-cta-band{text-align:center}` von selbst, genau wie in allen 22
311
- Mockup-Vorkommen. Kein `aria-label`-Input: anders als bei `cds-feature` (11 von 12
312
- mit `aria-label`) trägt keines der 22 Band-Vorkommen einen `aria-label` auf seiner
313
- Aktion, ausgezählt statt geschätzt. Die Aktion folgt demselben Muster wie bei
314
- `cds-feature`: gesetztes Href → echter `<a href>`, sonst `<button>` mit feuerndem
315
- Output, nie ein `<a>` ohne Ziel. Neue Bauteil-Ebene
316
- `Komponenten/Call to Action/CTA-Band` (Stories `Interaktiv`, `Pro Bereich`, `Als
317
- Links`), `storySort.order` der Sektion „Call to Action“ entsprechend ergänzt.
318
- **Nebenbefunde (CSS-/Gate-Kern, nicht in diesem Ticket behoben, jetzt eigene
319
- Tickets 15–17):** `.ep-cta-sub` (`opacity:.85`) unterschreitet auf `--co-700` den
320
- AA-Textkontrast (4,46:1 statt 4,5:1, WCAG-Formel gegen die Token-Werte gerechnet;
321
- axe meldet dasselbe in den Storybook-Interaktionstests) — auf
322
- `--ki-800`/`--es-700`/`--wo-700` liegt derselbe Text bei 6,1–7,4:1, „co“ ist der
323
- systematische Ausreißer, wie schon beim bekannten `.cta-dl-eyebrow`-Befund in
324
- `download-cta.stories.ts` (Ticket 15). Identisches rohes HTML (docs/index.html:6304
325
- ff.) hat denselben Fehler, kein Wrapper-Artefakt. Die betroffenen Stories setzen
326
- lokal `a11y: { test: 'todo' }` (dieselbe Ausnahme, die `preview.ts` für bekannte
327
- CSS-Kern-Befunde vorsieht). Zusätzlich: `check:contrast`
328
- (`scripts/check-contrast.mjs`) übersieht diesen Fall, weil es `opacity` nur als
329
- Ein/Aus-Schalter behandelt (Zeile 181) und nicht in die Farbmischung mit dem
330
- Hintergrund einrechnet — ein blinder Fleck des Gates (Ticket 17), kein Beleg dafür,
331
- dass der Befund neu wäre.
332
- - **`TierComponent` (`cds-tier`) und `FactsComponent` (`cds-facts`), achtes Ticket der
333
- Seitenbausteine-Serie: die Angebots-Bausteine (`cds-award-list` zurückgestellt,
334
- siehe unten).** `cds-tier` bildet `.ep-tier` ab (css/components.css:1381–1384):
335
- Label + Haarlinie, die eine Offene Feature-Liste in Pakete gliedert (z. B. „In
336
- jedem Paket enthalten“ vor „Zusätzlich mit Pro“). Element-Selektor, der
337
- ADR-0008-Standardfall — ausgezählt: keines der 4 `.ep-tier`-Vorkommen in
338
- `docs/index.html` sitzt in einem Grid/Flex, das seine Kinder streckt, keines
339
- trägt eine `col-*`-Klasse, das Tag variiert nicht. `area` ist auf `'ki'` typisiert,
340
- nicht auf die vollen vier Markenbereiche: `css/components.css` kennt
341
- ausschließlich `.ep-tier-label[data-area="ki"]` (Dark-Override
342
- `css/dark-mode.css:476`), für `co`/`es`/`wo` existiert keine einzige Regel —
343
- ausgezählt: 3 von 4 `.ep-tier-label`-Vorkommen setzen `ki`, eines gar kein
344
- `data-area`, keines einen anderen Bereich. Ein `CdsArea`-Input hätte für drei von
345
- vier gültigen Werten ein wirkungsloses `data-area`-Attribut geschrieben; der
346
- eingeschränkte Typ macht das strukturell unmöglich, statt es nur im JSDoc zu
347
- behaupten. `.ep-tier-rule` ist rein dekorativ und trägt `aria-hidden="true"`. Neue
348
- Bauteil-Ebene `Komponenten/Cards & Teaser/Tier-Trenner` (Stories `Interaktiv`,
349
- `Pro Bereich`), `storySort.order` der Sektion „Cards & Teaser“ entsprechend ergänzt.
350
- `cds-facts` bildet `.ep-facts` ab (css/components.css:1261–1272): die
351
- Definitionsliste für Angebots-Eckdaten (Termin, Dauer, Ort, Preis), einspaltig mit
352
- Haarlinie zwischen den Paaren oder als `.is-grid` zweispaltig für Kästen neben
353
- Inhalt. Ebenfalls Element-Selektor: analog zu `cds-faq` trägt die INNERE `<dl
354
- class="ep-facts">` die CSS-Klasse, nicht der Host — die Semantik einer
355
- Definitionsliste hängt am `<dl>`-Tag selbst, ein Custom-Element kann es nicht
356
- annehmen; da `.ep-facts > div + div` nur Nachfahren der eigenen Vorlage anspricht,
357
- bricht die zusätzliche Host-Ebene nichts (kein „Fläche am Host“-Fall wie bei
358
- `cds-section`). Jedes Fakten-Paar rendert deshalb exakt als `<div><dt>…</dt>
359
- <dd>…</dd></div>`, weil genau dieser Selektor den Trenner ab dem zweiten Paar
360
- setzt. Über die Ticket-Skizze hinaus ergänzt: ein `area`-Input, der `.t-{area}`
361
- auf jedes `<dt>` anwendet — der CSS-Kommentar zu dieser Klasse
362
- (css/components.css:1259) nennt `.t-XX` ausdrücklich als sanktionierten Weg für
363
- den Bereichston der Labels, und alle 3 realen `.ep-facts`-Vorkommen in
364
- `docs/index.html` setzen ihn einheitlich (`.t-wo`) auf jedem `<dt>` derselben
365
- Liste; ohne den Input hätte sich keines originalgetreu nachbauen lassen. Neue
366
- Bauteil-Ebene `Seitenmuster/Seminar · Training/Fakten-Liste` (Stories
367
- `Interaktiv`, `Als Raster`), `storySort.order` entsprechend ergänzt — die Sektion
368
- „Seminar · Training“ wechselt dabei von einer flachen Doku-Seite zu einer Sektion
369
- mit Bauteil-Ebene, `Übersicht` bleibt per `CONTRIBUTING.md` §12 das erste Kind.
370
- **`cds-award-list` zurückgestellt:** `.ep-award-list` (css/components.css:
371
- 1242–1245) kommt in `docs/index.html` außerhalb der Beispielseiten nicht vor — die
372
- einzige Erwähnung außerhalb ist eine Zeile der Responsive-Verhaltenstabelle
373
- (`docs/index.html:1963`), kein eigener Doku-Abschnitt mit Verwendung und
374
- Code-Beispiel nach `CONTRIBUTING.md` §10.4. Statt einen neuen Produktions-Doku-
375
- Abschnitt ungeprüft anzulegen, ist das Bauteil aus diesem Ticket herausgelöst;
376
- Entscheidung und nächster Schritt stehen in
377
- `.scratch/angular-seitenbausteine/issues/18-doku-luecke-ep-award-list.md`.
378
- - **Interaktions- und Tastaturtests für die bisher ungeprüften Komponenten.** Der Button, die drei
379
- Theme-Umschalter, die Footer-Aktion der Card, der Aktionsknopf der Snackbar und der Kopier-Button
380
- des CodeBlocks hatten keinen einzigen Interaktionstest, obwohl Stories laut
381
- [ADR-0005](docs/adr/0005-testebene-der-angular-lib.md) die einzige Testebene der Lib sind.
382
- Ebenso ungeprüft waren die Tastaturpfade von AreaTabs, Carousel und Select sowie der
383
- deaktivierte Zustand aller sieben Formularkomponenten. 110 Tests statt 89.
384
- - **`--font-mono` als dritte Schriftrolle des Systems.** Das System hatte Tokens für Grotesk
385
- (`--font`) und Serife (`--font-display`), aber keines für die dicktengleiche Schrift — die stand
386
- stattdessen zehnmal hart in `css/base.css` und `css/components.css` und weitere zehnmal in
387
- Inline-Styles von `docs/index.html`. Der Wert bleibt unverändert `'Courier New',monospace`, es
388
- ändert sich kein gerendertes Pixel; der Schritt ist rein strukturell. Sein Zweck: die Frage, ob
389
- Courier New die richtige Bildschirmschrift für Code ist, fällt ab jetzt an einer Stelle statt an
390
- zwanzig. Storybooks Doku- und Manager-Chrome (`fontCode` in `.storybook/theme.ts`) zeigt auf
391
- denselben Stack, damit Doku-Site und Storybook denselben Code-Satz rendern.
392
- - **`TableComponent` (`cds-table`), neuntes Ticket der Seitenbausteine-Serie.** Wrapper um
393
- `.tbl`/`.tbl-wrap` (css/components.css:1462–1480): die Datentabelle mit horizontalem
394
- Scroll-Container. Ausgezählt: außerhalb der Doku-Sektion `sec-table` selbst kommt
395
- `.tbl-wrap` nur zweimal vor, beide auf Beitragsseiten (`docs/index.html:8622`, `15080`).
396
- **Kein Daten-Input** —
397
- eine `columns`/`rows`-API hätte Zellinhalte auf Strings festgelegt, die Beispielseiten setzen
398
- darin Badges, Links und `data-num`; der Konsument projiziert `<thead>`/`<tbody>`/`<tfoot>`
399
- unverändert per `<ng-content>`, die Komponente liefert nur `.tbl-wrap`, `.tbl`, `<caption>` und
400
- `.tbl--striped`. Element-Selektor, ADR-0008-Standardfall wie `cds-facts`/`cds-faq`: ausgezählt,
401
- keines der 5 `.tbl-wrap`-Vorkommen in `docs/index.html` (Zeilen 5061, 5116, 5236, 8622, 15080)
402
- ist selbst ein direktes Grid-/Flex-Kind, keines variiert das Tag — `.tbl-wrap`/`.tbl` sitzen
403
- deshalb im eigenen Template der Komponente, nicht am Host. Im laufenden Storybook geprüft: die
404
- gerenderte Kette hat `<table class="tbl">` direkt unter `<div class="tbl-wrap">`, `<caption>`
405
- als erstes Kind, danach ohne zusätzlichen Knoten die projizierten `<thead>`/`<tbody>`-Elemente —
406
- `<ng-content>` fügt selbst kein DOM-Element ein. `caption` ist `input.required<string>()`,
407
- abweichend von der ursprünglichen Ticket-Skizze (dort optional mit Default `''`): die eigene
408
- Doku (`tabelle.mdx:137`) nennt `<caption>` „Pflicht, Screenreader lesen den Titel vor, bevor
409
- die Zellen vorgelesen werden“, und alle 5 realen `.tbl`-Vorkommen in `docs/index.html` haben
410
- eine — nach ADR-0007 §2 ist `caption` damit Inhalt, nicht Beiwerk. `.tbl-wrap` ist deshalb
411
- immer per Tastatur erreichbar (`tabindex="0"`, eigener `:focus-visible`-Ring) UND immer
412
- benannt (`role="region"` + `aria-label`, `scrollLabel` hat Vorrang, sonst `caption`, kann dank
413
- Pflicht-`caption` nie leer werden). **`.tbl-sort` bleibt außen vor:** die Klasse liefert nur den
414
- Button-Look, es gibt keine zugehörige Sortierlogik in `docs/main.js` — ein Wrapper, der sie
415
- anböte, täuschte Funktion vor, die nicht existiert. Neuer Befund
416
- `.scratch/angular-seitenbausteine/issues/19-fehlende-sortierlogik-tbl-sort.md`. Neue
417
- Bauteil-Ebene `Komponenten/Tabelle/Tabelle` (Stories `Interaktiv`, `Gestreift`,
418
- `Mit Zahlenspalte`, `Mit Fußzeile`, `Schmaler Viewport`), `storySort.order` und
419
- `SECTION_ICON_KEYS` entsprechend ergänzt — die Sektion „Tabelle“ wechselt dabei von einer
420
- flachen Doku-Seite zu einer Sektion mit Bauteil-Ebene, `Übersicht` bleibt per
421
- `CONTRIBUTING.md` §12 das erste Kind. `storybook-angular/src/docs/komponenten/tabelle.mdx`
422
- entsprechend nachgezogen (die veraltete Aussage „kein eigenes Angular-Bauteil“ galt nur noch
423
- für die separat zu bauende Vergleichstabelle, `.ep-compare*`, Ticket 10).
424
- - **`CompareComponent` (`cds-compare`), zehntes Ticket der Seitenbausteine-Serie.** Wrapper um
425
- `.ep-compare*` (css/components.css:1275–1294): die aufklappbare Vergleichstabelle für den
426
- zeilenweisen Direktvergleich mehrerer Pakete/Tarife hinter nativem `<details>`/`<summary>`.
427
- **Mit Daten-Input, anders als `cds-table`:** ausgezählt sind alle 22 Datenzellen des einzigen
428
- realen Vorkommens (`docs/index.html:11970–12038`, 11 Zeilen × 2 Spalten) entweder ein
429
- Ja/Nein-Marker (18×) oder eine kurze Angabe (4×), nie ein Badge oder Link — `columns`/`rows`
430
- statt `<ng-content>`, weil die Zell-Typisierung
431
- (`boolean | string`) dem Konsumenten ohnehin keine zusätzliche Freiheit ließe. Ja/Nein-Zellen
432
- tragen fest verdrahteten `.sr-only`-Text (`Enthalten`/`Nicht enthalten`, exakt aus dem Mockup
433
- übernommen, nicht erfunden, Glyphen „✓“/„−“ U+2212), weil Farbe und Glyphe allein keine
434
- Bedeutung tragen (WCAG 1.4.1). `caption` ist `input.required<string>()`, dieselbe Begründung
435
- wie bei `cds-table`: die eigene Doku (`tabelle.mdx:91`) nennt `<caption class="sr-only">`
436
- ausdrücklich als Teil des Bauteils. Anders als bei `cds-table` ist sie hier `.sr-only`, weil der
437
- Klartext schon im `<summary>` steht. `rowsLabel` (erste Kopfzelle, „Funktion“ im Mockup) ist
438
- dagegen Beiwerk mit Default `''`: ein leeres `<th scope="col">` bleibt eine gültige, in
439
- Vergleichsmatrizen verbreitete Ecke, das Beispielwort steht deshalb in der Story statt in der
440
- Klasse. Natives `<details>`/`<summary>` bleibt erhalten (Tastaturbedienung und Toggle kommen
441
- vom Browser, das CSS hängt an `[open]`), `<details class="ep-compare">` sitzt deshalb im
442
- Template, nicht am Host — ein `<cds-compare>`-Host ist ein unbekanntes Custom Element und kann
443
- echte `<details>`-Semantik nicht annehmen (derselbe Grund wie bei `cds-table`s `.tbl-wrap`).
444
- Element-Selektor, ADR-0008-Standardfall: `.ep-compare` ist im einzigen realen Vorkommen ein
445
- gewöhnlicher Block-Nachfahre in `.ep-section`, kein Grid-/Flex-Kind, kein Tag-Wechsel. `open`
446
- ist reiner Anfangszustand über `[attr.open]="open() ? '' : null"`, keine Zwei-Wege-Bindung —
447
- Angular schreibt das Attribut nur bei einer Werteänderung des geprüften Ausdrucks, ein
448
- nachfolgender nativer Toggle bleibt deshalb unangetastet (in der Story „Interaktiv“ per
449
- Play-Funktion geprüft, nicht nur angenommen). `toggled` feuert bei jedem Auf-/Zuklappen mit dem
450
- neuen Zustand. Neue Bauteil-Ebene `Komponenten/Tabelle/Vergleichstabelle` (Stories `Interaktiv`,
451
- `Bereits offen`, `Mit Text-Zellen`, `Hervorgehobene Spalte`), `storySort.order` der Sektion
452
- „Tabelle“ entsprechend ergänzt (`['Übersicht', 'Tabelle', 'Vergleichstabelle']`),
453
- `storybook-angular/src/docs/komponenten/tabelle.mdx` nachgezogen. Befund zur Zeilenangabe im
454
- Ticket: `.scratch/angular-seitenbausteine/issues/20-ticket-10-falsche-zeilenangabe-doku-vorlage.md`.
455
- - **`ArticleHeaderComponent` (`cds-article-header`), `AvatarComponent` (`div[cdsAvatar]`) und
456
- `AvatarStackComponent` (`cds-avatar-stack`), elftes Ticket der Seitenbausteine-Serie.** Wrapper
457
- um `.article-header`/`-breadcrumb*`/`-title`/`-lead`/`-meta*` (css/components.css:1486–1519) und
458
- `.article-avatar*` (css/components.css:1520–1545): der zentrierte Kopf eines Wissensbeitrags samt
459
- Avatar-Stapel für mehrere Autor:innen. Hauptvorlage war
460
- `storybook-angular/src/docs/seitenmuster/wissensbeitrag.mdx` (Abschnitt „Article Header“), nicht
461
- nur das Mockup. **Avatar bekommt einen Attributselektor** (`div[cdsAvatar]`, ADR-0008): ausgezählt
462
- sitzen 8 der 11 Grundgrößen-Instanzen in `docs/index.html` als direkte Geschwister in einer von
463
- 3 `.article-avatar-stack`-Kacheln (Zeilen 8343–8344, 8373–8375, 8404–8406), wo der
464
- CSS-Geschwister-Selektor
465
- `.article-avatar-stack .article-avatar + .article-avatar` (css/components.css:1540) einen echten,
466
- gemeinsamen Elternknoten braucht — ein Element-Selektor, der `.article-avatar` auf ein inneres
467
- Element legt (wie `cds-facts` es mit `<dl>` tut), würde die Kette brechen. Gemessen per
468
- `getBoundingClientRect()` in der Story „Avatar-Stapel“ (28 statt 40 px Abstand der linken Kanten
469
- bei 40-px-Kacheln und `margin-left:-12px`) UND per Screenshot-Baseline
470
- (`visual-snapshots/…avatar-stapel.png`, Kreise überlappen sichtbar mit weißem Halo-Rand) — nicht
471
- nur an Computed Styles (ADR-0008, Fall 2). Zweiter, unabhängiger Grund für den Attributselektor:
472
- `cds-article-header` projiziert den Avatar über `<ng-content select="[cdsAvatar], cds-avatar-stack">`,
473
- dasselbe Attribut dient also zugleich als Host-Selektor UND als Projektions-Marker. `cds-pill`
474
- bleibt dagegen eine echte Komponente (kein direkt komponiertes `<span class="pill">`): geprüft per
475
- `docs show --id komponenten-chips-badges-pills-pill`, `PillComponent`s `.pill` sitzt zwar eine
476
- Ebene unter seinem Host, `.article-header`/`.pill` haben aber (anders als `FeaturedCardComponent`s
477
- `.card-featured-body>.pill`) keinen Kindselektor, der das verhindern würde — per Screenshot
478
- bestätigt (`visual-snapshots/…interaktiv.png`). Neuer Input `pillAriaLabel` (wie bei
479
- `FeaturedCardComponent`), weil `PillComponent`s eigener Default („Bereich `<label>`“) für die
480
- Lesezeit-Pille des Wissensbeitrags nicht passt (`wissensbeitrag.mdx`: `aria-label="Lesezeit 8
481
- Minuten"`) und aus einem freien `pill`-String nicht zuverlässig herleitbar ist. **Die Pille
482
- rendert nur, wenn `area` UND `pill` gesetzt sind** — ausgezählt trägt jedes der 9 realen
483
- `.pill`-Vorkommen im Article-Header-Kontext ein `data-area`, ein Fall ohne Bereich kommt im
484
- Mockup nicht vor; statt einen Default zu erfinden (etwa `PillComponent`s eigenen, per
485
- `strictTemplates`-Typüberbrückung durchgereichten), narrowt `@if (area(); as pillArea)`
486
- `CdsArea | undefined` sauber auf `CdsArea`, ohne Typ-Cast, und ohne `area` bleibt die Pille
487
- schlicht weg (Story „Pille ohne Bereich“). **Initialen werden
488
- aus `name` abgeleitet** (`computed()`, erstes Zeichen von erstem und letztem Wort), kein eigener
489
- Input. **`date`/`dateLabel` bleiben getrennt, anders als `cds-stoerer`**: `cds-stoerer` formatiert
490
- ein ISO-Datum selbst nach `de-DE` (inkl. Umgehung des UTC-Mitternacht-Fallstricks bei
491
- `new Date(iso)`); diese Komponente konstruiert dagegen nie ein `Date`-Objekt aus `date()` —
492
- `dateLabel` liefert die sichtbare Schreibweise, ohne `dateLabel` steht `date` unverändert als
493
- ISO-Text sichtbar (in der Story „Interaktiv“ als Warnfall demonstriert), der Zeitzonen-Fallstrick
494
- kann so gar nicht erst auftreten. `aria-hidden="true"` ist an beiden Avatar-Komponenten fest
495
- verdrahtet (kein Input): ausgezählt tragen 45 von 53 `.article-avatar`-Instanzen es direkt, die
496
- restlichen 8 sitzen in einem bereits `aria-hidden`-tragenden Stapel (redundant, aber unschädlich).
497
- Breadcrumb: letzter Eintrag rendert immer ohne Link mit `aria-current="page"`
498
- (Akzeptanzkriterium), unabhängig von den beiden unvollständigen Wissensbeitrag-Inline-Beispielen,
499
- die dafür keine Vorlage sind (siehe Befund unten). Meta-Strip rendert nur, wenn `authorName`
500
- und/oder `date` etwas liefern (eine Stellenanzeige nutzt `.article-header` komplett ohne
501
- `.article-meta`, das Ticket sieht dafür keine API vor). Neue Bauteil-Ebenen
502
- `Seitenmuster/Wissensbeitrag/Article-Header` (Stories `Interaktiv`, `Ohne Breadcrumb`, `Mehrere
503
- Autor:innen`) und `Seitenmuster/Wissensbeitrag/Avatar` (Stories `Avatar-Größen`,
504
- `Avatar ohne Bild`, `Avatar mit Bild`, `Avatar-Stapel`), `storySort.order` der Sektion
505
- „Wissensbeitrag“ entsprechend ergänzt (`['Übersicht', 'Article-Header', 'Avatar', 'FAQ']`),
506
- Doku-Seite bleibt erstes Kind. Befunde:
507
- `.scratch/angular-seitenbausteine/issues/20-breadcrumb-in-oder-vor-article-header.md`
508
- (`docs/index.html:2423`, wörtlich: „Den Breadcrumb nicht in einen zentrierten `.article-header`
509
- einbetten…“ — beide echten Beispielseiten folgen dem, 6 isolierte Doku-Demos inkl.
510
- `wissensbeitrag.mdx` widersprechen dem ausdrücklich — die Komponente folgt der Ticket-Vorlage) und
511
- `.scratch/angular-seitenbausteine/issues/21-pill-default-area-dokumentation-vs-code.md`
512
- (`PillComponent.area` dokumentiert einen Corporate-Default, der Code liefert `'ki'`).
513
- - **`ArticleTocComponent` (`cds-article-toc`), `ArticleCalloutComponent` (`cds-article-callout`),
514
- `ArticleFigureComponent` (`cds-article-figure`) und `ArticlePullquoteComponent`
515
- (`cds-article-pullquote`), zwölftes Ticket der Seitenbausteine-Serie („Artikel-Körper“).**
516
- Wrapper um `.article-toc*` (css/components.css:1577–1589), `.article-callout*`
517
- (css/components.css:1591–1600), `.article-figure`/`.article-figcaption`
518
- (css/components.css:1573–1575) und `.article-pullquote` (css/components.css:1608–1612): die
519
- vier Bausteine im Lauftext eines Wissensbeitrags. `.article-body` selbst bekommt bewusst
520
- **keine** Komponente — reiner Typografie-Kontext, dessen Kindselektor (`.article-body > p`)
521
- eine Zwischenebene stören würde; der Konsument schreibt weiterhin `<div class="article-body">`
522
- selbst (neue Story „Im Artikel-Body“ in `Seitenmuster/Wissensbeitrag/Inhaltsverzeichnis`, zeigt
523
- alle vier Bausteine im rohen Kontext). Alle vier bleiben Element-Selektoren
524
- (ADR-0008-Standardfall): ausgezählt sitzt keines der insgesamt 12 realen Vorkommen als
525
- Grid-/Flex-Kind, keines trägt eine `col-*`-Klasse, keines steht in einem
526
- Geschwister-Kombinator, kein Tag variiert (immer `<details>`/`<aside>`/`<figure>`/
527
- `<blockquote>`) — je eigens geprüft, nicht pauschal übernommen. **`cds-article-toc` ist das
528
- zweite Vorkommen des `<details>`-mit-drehendem-Caret-Musters neben `cds-compare` (Ticket 10),
529
- bewusst NICHT zusammengezogen (ADR-0007 §5):** natives `<details>`/`<summary>`, `aria-label`
530
- am `<summary>` fest verdrahtet (wortgleich in beiden realen Vorkommen), kein `toggled`-Output
531
- (anders als `cds-compare`, weil die Ticket-API keinen vorsieht). **`cds-article-callout`
532
- projiziert Absätze als direkte Kinder** (`.article-callout > p`, Ticket-Vorgabe): `<ng-content>`
533
- fügt kein eigenes Element ein, geprüft per `:scope > p` in der Story „Interaktiv“. Dabei ein
534
- bekannter, vorbestehender CSS-Befund entdeckt und NICHT im Wrapper geflickt (ADR-0001):
535
- `.article-callout-eyebrow` (Spezifität 0,1,0) verliert gegen `.article-callout > p`
536
- (Spezifität 0,1,1) bei Schriftgröße, -gewicht und Randabstand, in jedem Bereich, weil die
537
- Eyebrow selbst ein `<p>` UND ein direktes Kind ist — gemessen in rohem Markup ohne Angular
538
- (Playwright/Chromium), siehe
539
- `.scratch/angular-seitenbausteine/issues/22-css-luecke-callout-eyebrow-spezifitaet.md`.
540
- Zusätzlich trägt `<aside>` jetzt `aria-labelledby` auf die Eyebrow, sobald eine gesetzt ist
541
- (ARIA-Zusatz, keine CSS-Änderung): mehrere `.article-callout` auf derselben Seite wären sonst
542
- gleichnamige, ununterscheidbare `complementary`-Landmarks (axe `landmark-unique`, gemessen auch
543
- in rohem Markup ohne Angular, siehe
544
- `.scratch/angular-seitenbausteine/issues/23-doku-luecke-callout-landmark-label.md`).
545
- **`cds-article-pullquote` grenzt sich wechselseitig von `cds-blockquote` ab** (Ticket-Vorgabe):
546
- Pull-Quote zitiert den eigenen Lauftext ohne Attribution, Box oder Icon, Blockquote zitiert eine
547
- benannte dritte Person mit getönter Box und Quote-Icon — beide Klassendocs verweisen
548
- aufeinander. `quote` enthält die deutschen Anführungszeichen bereits als Teil des Texts
549
- (`quotes:none`, keine CSS-generierten Marken in der gesamten Zitat-Familie), die Komponente
550
- ergänzt keine eigenen. `area` (Callout und Pull-Quote) hat den verteidigbaren Default `'co'`:
551
- die Basisregel ohne `[data-area]` rendert bereits identisch zu `[data-area="co"]`. `loading="lazy"`
552
- ist an `cds-article-figure` fest verdrahtet (2 von 3 realen Vorkommen, der dritte ist ein
553
- captionsloses Lead-Bild außerhalb von `.article-body` mit `eager`/`fetchpriority`, für das die
554
- Ticket-API kein Input vorsieht). Neue Bauteil-Ebenen `Seitenmuster/Wissensbeitrag/
555
- Inhaltsverzeichnis` (Stories `Interaktiv`, `Bereits offen`, `Im Artikel-Body`), `…/Callout`
556
- (`Interaktiv`, `Pro Bereich`, `Ohne Eyebrow`), `…/Figure` (`Interaktiv`, `Ohne Caption`) und
557
- `…/Pull-Quote` (`Interaktiv`, `Pro Bereich`), `storySort.order` der Sektion „Wissensbeitrag“
558
- entsprechend ergänzt (`['Übersicht', 'Article-Header', 'Avatar', 'Inhaltsverzeichnis',
559
- 'Callout', 'Figure', 'Pull-Quote', 'FAQ']`), Doku-Seite bleibt erstes Kind.
560
- - **`AuthorCardComponent` (`div[cdsAuthorCard]`) und `AuthorCardGroupComponent`
561
- (`cds-author-card-group`), dreizehntes und letztes Ticket der Seitenbausteine-Serie.**
562
- Wrapper um `.author-card*` (css/components.css:1602–1606) und `.author-card-group*`
563
- (css/components.css:1547–1563): der Avatar-plus-Bio-Strip am Ende eines
564
- Wissensbeitrags, einzeln oder als gestapelte/`is-grid`-Gruppe. **`cds-author-card`
565
- bekommt einen Attributselektor** (ADR-0008), gemessen statt nur hergeleitet: eine
566
- Spike-Story hat den Element-Selektor- und den Attributselektor-Aufbau nebeneinander
567
- im `.is-grid`-Fall gebaut und mit `getBoundingClientRect()` verglichen — Element-
568
- Selektor liefert `.author-card`-Höhen von 84/204 px bei ungleich langen Bios (der
569
- unsichtbare Host wird auf 204 px gestreckt, die sichtbare Box eine Ebene darunter
570
- bleibt bei ihrer Inhaltshöhe), Attributselektor liefert 204/204 px. **Ehrlicher
571
- Unterschied zu `cds-icon-card`:** die Baseline-Screenshots beider Spike-Varianten
572
- waren byte-identisch (sha1 gleich) — `.author-card` hat anders als `.ep-card` weder
573
- Hintergrund noch Rahmen noch Schatten, der Fehler ist heute rein strukturell
574
- (falsches Boxmodell), nicht optisch sichtbar, wird aber real, sobald die Karte
575
- künftig Rahmen/Hintergrund/Hover bekommt, und verfälscht schon heute jede Messung
576
- auf die tatsächliche Kartenhöhe. `cds-author-card-group` bleibt Element-Selektor
577
- (ADR-0008-Standardfall für die Gruppe selbst), setzt `.author-card-group` aber auf
578
- ein INNERES `<div>` statt auf den Host: `.author-card-group-eyebrow:has(+
579
- .author-card-group.is-grid)` (css/components.css:1563) verlangt, dass die
580
- Gruppen-Eyebrow ein unmittelbares Geschwister von `.author-card-group` bleibt, was
581
- mit der Klasse auf dem Host (Eyebrow müsste dann als Kind darin stehen und würde im
582
- Raster zur eigenen Grid-Zelle) nicht ginge. **`area` ist bewusst KEIN Input von
583
- `cds-author-card`** — ausgezählt tragen alle 17 realen `.author-card`-Vorkommen
584
- `data-area`, und sowohl `docs/index.html:8185` als auch `wissensbeitrag.mdx:199`
585
- behaupten wörtlich, das färbe „nur den Avatar“; tatsächlich hat `css/components.css`
586
- (Light UND Dark) keine einzige `.author-card[data-area]`-Regel, die Bereichsfarbe
587
- kommt ausschließlich vom `area`-Input des projizierten `[cdsAvatar]` (Ticket 11) —
588
- ein Input, der hier nur ein wirkungsloses Attribut setzt, wäre eine erfundene
589
- Konfiguration ohne Gegenwert (Befund, nicht im Wrapper geflickt:
590
- `.scratch/angular-seitenbausteine/issues/24-css-luecke-author-card-data-area.md`).
591
- **`roleLabel`, nicht `role` wie im Ticket-Text** (gemessene Abweichung): ein erster
592
- Entwurf mit `role`-Input ließ axe bei jeder Story mit echtem Rollentext auf
593
- `aria-roles` fehlschlagen, weil ein ungebundenes `role="…"`-Attribut zusätzlich als
594
- natives ARIA-`role` im DOM stehen bleibt — dieselbe Kollision, die
595
- `BlockquoteComponent`/`TestimonialComponent`/`TeamVoiceComponent` bereits mit
596
- `roleLabel` umgehen; diese Komponente folgt demselben, etablierten Muster. Avatar
597
- projiziert über `[cdsAvatar]`, wie bei `cds-article-header`: Größe/Bereich setzt der
598
- Konsument direkt am Avatar. Eyebrow rendert als `<h3>`, nur wenn gesetzt — sowohl
599
- an der Einzelkarte (`.author-card-eyebrow`) als auch an der Gruppe
600
- (`.author-card-group-eyebrow`), deckungsgleich mit den realen, vollständigen
601
- Mockup-Seiten (die isolierte `<p>`-Variante einer Doku-Illustration ohne eigene
602
- `<h2>`-Hierarchie bleibt außen vor). Neue Bauteil-Ebene
603
- `Seitenmuster/Wissensbeitrag/Author-Card` (Stories `Interaktiv`, `Zwei Autor:innen`,
604
- `Als Raster`, `Ohne Bio` — „Als Raster“ mit bewusst ungleich langen Bios, pinnt
605
- gleiche Kartenhöhen per `getBoundingClientRect()`), `storySort.order` der Sektion
606
- „Wissensbeitrag“ entsprechend ergänzt (`['Übersicht', 'Article-Header', 'Avatar',
607
- 'Inhaltsverzeichnis', 'Callout', 'Figure', 'Pull-Quote', 'FAQ', 'Author-Card']`),
608
- Doku-Seite bleibt erstes Kind.
609
-
610
- ### Changed
611
- - **`npm run test` und `npm run playwright:install` laufen auch unter Windows.** Beide Skripte
612
- setzten `PLAYWRIGHT_BROWSERS_PATH=0` in Unix-Syntax, an der `cmd.exe` scheiterte; sie gehen
613
- jetzt über `cross-env` (neue devDependency in `storybook-angular`).
614
- - **`storybook-angular/vitest.config.ts` und `visual-baseline-guard.ts` heißen jetzt `.mts`.** Vite
615
- warnte, beide nutzten ESM-Syntax, würden aber als CommonJS geladen, und ein Import ohne
616
- Dateiendung vertrage sich nicht mit dem nativen Config-Loader, den Vite künftig zum Default
617
- machen will. `"type": "module"` für den ganzen Workspace kam nicht infrage, weil
618
- `eslint.config.js` dort CommonJS ist. Den Import mit `.mts`-Endung akzeptiert TypeScript nur mit
619
- `allowImportingTsExtensions`; die beiden Dateien bekommen dafür eine eigene
620
- `tsconfig.node.json`, auf die `tsconfig.json` per `references` verweist, damit Editoren sie
621
- ohne Fehlermeldung prüfen.
622
- - **Storybook-Workspace führt `@angular/animations` und `@angular/platform-browser-dynamic`
623
- nicht mehr als eigene Abhängigkeiten.** Beide sind seit Angular 22 als veraltet markiert, und
624
- weder die Stories noch das Framework importieren sie. `@angular/animations` bleibt im Baum,
625
- weil `@storybook/angular-vite` es als Pflicht-Peer verlangt; npm installiert es darüber mit.
626
- Betrifft nur das interne Storybook, nicht die ausgelieferten Pakete.
627
- - **Visual-Regression von Storybook-Test-Runner (Jest) nach Vitest verschoben, eine Testschiene
628
- statt zwei.** `npm run test:vitest` deckte bereits Smoke-Rendering, `play`-Funktionen und die
629
- a11y-Prüfung ab; der zweite Lauf über `@storybook/test-runner` (`test-storybook:ci`) fuhr
630
- dieselben 110 Tests redundant noch einmal und trug nur die Visual-Regression bei
631
- (`jest-image-snapshot` in `.storybook/test-runner.ts`). Diese Logik steckt jetzt in einem
632
- `afterEach`-Hook in `.storybook/vitest.setup.ts` (`expect(document.body).toMatchScreenshot(...)`,
633
- nur mit `VISUAL=1`, Baselines weiterhin unter `visual-snapshots/<story-id>.png`,
634
- `parameters.snapshot.skip` als Opt-out). `test-storybook`, `test-storybook:ci`,
635
- `test-storybook:visual` sowie die Root-Skripte `test:storybook`, `test:storybook:ci`,
636
- `test:visual` und die Pakete `@storybook/test-runner`, `jest-image-snapshot`,
637
- `@types/jest-image-snapshot`, `http-server`, `wait-on`, `concurrently` sind entfernt. Grund: der
638
- ESM-Loader-Hook, den Jest beim Laden von `test-runner.ts` registriert, bricht unter Angular 22 in
639
- allen Stories ab (unter Angular 21 nur eine Deprecation-Warnung) — Vitest ist der von Angular
640
- vorgesehene Testrunner, die zweite Schiene abzulösen entfernt den Blocker, statt ihn zu reparieren.
641
- `.github/workflows/visual.yml` (kein `http-server`/`wait-on` mehr, Vitest startet seinen Server
642
- selbst) und `.github/workflows/storybook-angular.yml` (Schritt „Storybook-Tests“ läuft jetzt über
643
- `test:vitest`) sind entsprechend angepasst. Details und die Arbeitsteilung, die damit endet, in
644
- [ADR-0005](docs/adr/0005-testebene-der-angular-lib.md).
645
- - **Storybook auf 10.6.0, Docgen-Server statt Compodoc.** Alle Storybook-Familienpakete
646
- in `storybook-angular/` sind auf `^10.6.0`, `@storybook/test-runner` auf `^0.24.5`.
647
- Der seit 10.6 in `@storybook/angular-vite` default gesetzte In-Process-Docgen-Server
648
- liest Inputs, Outputs und JSDoc direkt aus der TypeScript-Quelle der Angular-Lib und
649
- speist damit Controls, Docs-Seiten und das Komponenten-Manifest (siehe nächster
650
- Punkt). Die nie produktiv genutzte Compodoc-Pipeline ist entfernt (`compodoc`/
651
- `compodocArgs` raus aus `main.ts` und den `angular.json`-Builder-Optionen). Details
652
- und verworfene Alternativen in
653
- [ADR-0006](docs/adr/0006-storybook-10-6-docgen-server-mcp-und-theming.md).
654
- - **`@storybook/addon-mcp` aktiv: Komponenten-Manifest und CI-Gate dafür.**
655
- `npm run build-storybook` schreibt jetzt `storybook-static/manifests/components.json`
656
- (`meta.docgen: "angular-component-meta"`), der Dev-Server beantwortet unter `/mcp`
657
- JSON-RPC für Agenten (u. a. `stories-preview`, `docs-show`, `test-run`). Die CI prüft
658
- nach dem Build, dass dieses Manifest existiert und die erwartete Kennung trägt — ein
659
- künftiges Update kann das Manifest damit nicht mehr stillschweigend abschalten.
660
- - **JSDoc `@internal` in der Angular-Lib gegen Interna in Props-Tabellen und Manifest.**
661
- Der Docgen-Server dokumentiert grundsätzlich jedes öffentliche Member einer
662
- Komponentenklasse; ohne Filter erschienen Template-Getter,
663
- ControlValueAccessor-Methoden und Event-Handler in Docs-Props-Tabelle und Manifest.
664
- Alle Nicht-API-Member der Lib tragen jetzt `@internal` und bleiben damit aus beiden
665
- heraus — Agenten sehen über den MCP-Endpunkt dieselbe bereinigte API wie
666
- Entwickler:innen im Docs-Panel.
667
- - **Storybook-Manager und Docs-Chrome im Conciso-Look.** Sidebar, Toolbar und
668
- Docs-Seiten tragen jetzt Montserrat, die Conciso-Wortmarke und Corporate-Teal als
669
- Akzent (`.storybook/theme.ts`, `manager.ts`, `manager-head.html`). Der Manager folgt
670
- `prefers-color-scheme`, die Docs-Chrome bleibt fest im Light-Theme — beide sind
671
- bewusst vom Toolbar-Theme-Schalter der Preview entkoppelt, der weiterhin nur die
672
- Story-Vorschau steuert.
673
- - **Alle Komponenten laufen mit `OnPush` und leiten Werte über `computed()` ab.** Abgeleitete
674
- Werte steckten bisher in Gettern, die bei jedem Change-Detection-Lauf neu rechneten; das
675
- explizite `standalone: true` war seit Angular 19 Rauschen. Template-Handler und interne
676
- Zustandssignale sind jetzt `protected`, Legacy-Decorators sind den heutigen APIs gewichen. Die
677
- Regeln dahinter stehen in [ADR-0007](docs/adr/0007-api-konventionen-der-angular-komponenten.md).
678
- Für Konsumenten ändert sich am Verhalten nichts.
679
- - **Barrierefreiheits- und Verhaltenskorrekturen.** Die Dots des LogoCarousel waren als Tab-Leiste
680
- ausgezeichnet, ohne auf Pfeiltasten zu reagieren. Die Topnav gab beim Schließen per Escape den
681
- Fokus nicht an den öffnenden Knopf zurück, obwohl das CSS das Menü ausblendet — der Fokus fiel
682
- ins Nichts. Die Combobox öffnete nicht auf Pfeil nach oben, kannte kein Pos1 und Ende und ließ im
683
- Mehrfachmodus losen Filtertext stehen. Der DownloadCta hatte überhaupt keinen Output, ein Klick
684
- auf seine Hauptaktion verpuffte.
685
- - **Doku-Darstellung in Sidebar und Doku-Seiten aufgeräumt, vier kleine Korrekturen.** Das
686
- Inhaltsverzeichnis der Doku-Seiten (`docs.toc` in `preview.ts`) erfasste per Default nur
687
- `h3`, die 34 MDX-Seiten gliedern aber mit `##` (h2) — ganze Seiten ohne h3 hatten dadurch
688
- gar kein Inhaltsverzeichnis; jetzt `h2, h3`. Der Autodocs-Eintrag jeder Komponente hieß
689
- „Docs“, die einzige englische Zeile in einer durchgehend deutschen Navigation — jetzt
690
- „Übersicht“ (`docs.defaultName` in `main.ts`), das Wort, das die eigenständigen MDX-Seiten
691
- schon tragen. „Beispielseiten“ stand als einziges Wurzel-Blatt ohne Ordner ganz oben in der
692
- Sidebar vor „Marke“, weil Storybook Blätter vor Ordnern sortiert — jetzt ein Ordner mit dem
693
- Kind „Übersicht“ (`beispielseiten.mdx`), analog zu „Referenzen/Quellen“. Und die drei am
694
- wenigsten befüllten Wurzeln (Seitenmuster, Beispielseiten, Referenzen) starten zugeklappt
695
- (`collapsedRoots` in `manager.tsx`) — 149 der 181 Sidebar-Einträge hängen unter
696
- „Komponenten“, das bleibt wie Marke und Grundlagen offen.
697
- - **Doku-Seiten von Storybook an die Typografie der Doku-Site angeglichen.** Die Doku-Chrome
698
- brachte ihre eigene Emotion-Typografie mit: Überschriften in Montserrat 700 (32/24/20 px),
699
- Fließtext 14 px — also weder die Display-Schrift der Marke noch die eigene Body-Größe. Maßstab
700
- ist jetzt `docs/index.html`, die gebaute Referenz, die ihr Aussehen aus denselben CSS-Dateien
701
- zieht: h1 wie `.sec-title` (Libre Baskerville 400, 2rem/2.5rem), h2 wie `.group-title`
702
- (1.5rem/2rem plus Haarlinie, 40 px darüber, 24 px Polster, 16 px darunter), ab h3 bewusst
703
- Grotesk (`--ty-title-sm`), Fließtext und Tabellenzellen 16 px mit `line-height:1.7` wie am
704
- `body` in `base.css`, Tabellenköpfe und die kräftige Kopf-Trennlinie wie `.doc-table`
705
- (`preview-head.html`). Feste Größen statt der fluiden `--ty-display-*`/`--ty-headline-*`-Tokens:
706
- die sind für Seiten gedacht, deren Breite mit dem Viewport wächst, eine Doku-Spalte ist
707
- breitenbegrenzt. Die Haarlinie über h2 ist dabei der eigentliche Abstandsgeber — die Doku-Site
708
- gliedert nicht mit Luft allein. Die Regeln sparen `.docs-story` und `.sbdocs-preview` aus: dort
709
- stehen gerenderte Komponenten, deren Darstellung ausschließlich aus der CSS-Schicht kommen darf.
710
- Storybooks eigene Sektionsüberschrift „Stories“ bleibt ebenfalls unangetastet.
711
- - **`<Unstyled>` schaltet die Doku-Typografie jetzt tatsächlich ab.** Die Typografie-Regeln der
712
- Doku-Seiten sparten bisher nur die Story-Vorschauen aus. Rohes Demo-Markup, das MDX-Seiten
713
- direkt einbetten, fiel weiter darunter: gemessen rendert ein
714
- `<h1 class="hero-image-caption-title">` als Doku-Überschrift (Libre Baskerville 32 px, dunkel)
715
- statt als Bauteil (`--ty-serif-md`, weiß, `line-height:1.25`) — die Doku-Regel gewinnt mit
716
- Spezifität (0,3,1) gegen die Bauteil-Klasse mit (0,1,0). Storybooks eigener Block `<Unstyled>`
717
- half nicht, weil er nur `<div class="sb-unstyled">` rendert und kein CSS mitbringt. Alle sieben
718
- Regeln sparen `.sb-unstyled` jetzt aus — dieselbe Konvention, die Storybooks eigenes Stylesheet
719
- dafür benutzt. Damit können Doku-Seiten echte Live-Beispiele einbetten, die aussehen wie auf
720
- der Site, statt sie in Code-Blöcken zu beschreiben.
721
- - **Theme-Umschalter wirkt jetzt auch auf Doku-Seiten ohne eingebettete Story.**
722
- `themeStore.setSilent()` stand ausschließlich im Story-Decorator, der pro Story-Render läuft.
723
- Auf den 34 eigenständigen MDX-Seiten (Icons, Hero, Tabelle, Buchungsformular, alle
724
- Seitenmuster, alle „Verwendung“-Seiten) rendert keine Story — dort schrieb niemand
725
- `data-theme` ans `<html>` des Preview-Frames, gemessen blieb es `null`, der Umschalter war
726
- wirkungslos. Ein Listener auf Modulebene hört jetzt zusätzlich auf `setGlobals` (Erst-Load)
727
- und `globalsUpdated` (jede Änderung) und setzt den Store direkt; beide Ereignisnamen und ihre
728
- Nutzlast wurden gegen den installierten Dist geprüft. `setSilent` benachrichtigt keine
729
- Abonnenten, deshalb entsteht keine Rückkopplung mit der Gegenrichtung Store→Toolbar — je
730
- Umschaltvorgang gezählt: genau ein `updateGlobals`, ein `globalsUpdated`. Der Decorator bleibt
731
- daneben stehen. Damit rendern Live-Beispiele auf Doku-Seiten im Dark Mode korrekt; die
732
- Doku-Chrome selbst bleibt weiterhin hell (bekannter Follow-up aus ADR-0006).
733
- - **Seitenleiste entrümpelt.** Storybooks Typ-Icons und das rote „A“-Badge sind weg: das Badge
734
- hängte an einem Tag, das 158 von 181 Einträgen tragen, und markierte damit nichts. Die Zeilen
735
- stehen auf 32 px statt 28, zwischen den Wurzelgruppen liegt Luft, und Ordner wie Blätter
736
- derselben Ebene beginnen auf derselben Textkante — vorher standen Blätter 14 px weiter rechts,
737
- was vom Icon verdeckt war und sie fälschlich als Kinder des Ordners darüber lesen ließ. Der
738
- Aufklapp-Chevron bleibt erhalten: er sitzt im selben Wrapper wie das Typ-Icon, weshalb die
739
- Regel das Icon selbst trifft und nicht den Wrapper. Mit dem Badge entfallen auch die
740
- Umbenennung von `manager.ts` zu `.tsx` und die `jsx`-Option in der `tsconfig.json`, die es
741
- allein nötig gemacht hatte. Wirkt erst nach einem Neustart — der Manager liest
742
- `manager-head.html` und `manager.ts` nur beim Prozessstart.
743
- - **Icons-Seite zeigt jetzt Icons.** Die Seite erklärte Icons auf 105 Zeilen, ohne ein einziges zu
744
- zeigen. Alle 74 Glyphen aus `icons/icons.json` rendern jetzt live, getrennt nach den fünf
745
- Bereichs-Glyphen in ihrer Bereichsfarbe und den 69 bereichsneutralen UI-Icons, jeweils mit dem
746
- Schlüssel, den ein Entwickler importiert. Dabei fielen drei Aussagen der Seite als falsch auf
747
- und wurden korrigiert: die Registry hält je Schlüssel genau eine Variante, nicht fünf Größen;
748
- die Größe wird über `width`/`height` oder CSS gesetzt, nicht durch Wahl einer Variante; und die
749
- Farbe ist nicht eingebrannt, sondern kommt über `currentColor` vom Consumer. Die fünf
750
- handoptimierten Größenvarianten existieren nur als Inline-SVG in `docs/index.html`, nicht im
751
- veröffentlichten Paket.
752
- - Git LFS für `docs/assets/images/` + History-Bereinigung (entfernt die ~159 MB
753
- Bilder aus dem Git-Verlauf). Erfordert `git lfs` (noch nicht installiert) und
754
- `git lfs migrate` bzw. `git filter-repo` — schreibt die History um (Force-Push,
755
- Team-Koordination), daher bewusst als separater Schritt.
756
- - Optionales schlankes `behaviors.js` (Theme/Nav/Back-to-Top) fürs Paket.
757
-
758
- ### Fixed
759
- - **Listen in `CarouselComponent`, `FaqComponent`, `FooterBottomComponent` und
760
- `TopnavComponent` brechen bei doppelten Beschriftungen nicht mehr ab.** Die `@for`-Schleifen
761
- trackten per Titel, Frage bzw. Label, obwohl die Inputs keine Eindeutigkeit verlangen. Zwei
762
- gleich beschriftete Einträge lösten Angulars NG0955 aus und stoppten das Rendern. Carousel,
763
- Footer und Topnav tracken jetzt per `$index` (ihr Zustand hängt ohnehin am Index), das FAQ
764
- per Eintragsobjekt, damit der native `open`-Zustand eines `<details>` beim Umsortieren an
765
- seiner Frage bleibt. Je Komponente pinnt eine Story mit doppelten Beschriftungen das ab.
766
-
767
- ## [1.0.0] - 2026-08-21
768
-
769
- ### Fixed
770
- - **Das Card-Layout stand im `style`-Attribut, nicht in der Klasse.** `.card-body` dokumentiert
771
- `.card-cta-link--pinned`, und dessen `margin-top:auto` funktioniert ausschließlich im
772
- Flex-Container. Die Klasse war aber nur `padding:var(--s5)`. Ergebnis: in den Doku-Mockups stand
773
- die Flex-Spalte 43-mal im `style`-Attribut des Bodys, 41-mal zusätzlich an `.card` selbst (ohne
774
- Flex-Spalte an der Karte greift `flex:1` am Body ins Leere), dazu 41-mal `flex:1` und 39-mal ein
775
- `margin-bottom:0` an der Pill. Wer das dokumentierte Rezept ohne diese Inline-Styles übernahm,
776
- bekam ein anderes Layout als die Doku zeigt: keine Abstände zwischen den Body-Kindern und einen
777
- CTA, der nicht an der Unterkante sitzt.
778
- Jetzt tragen die Klassen das Layout: `.card` ist `display:flex;flex-direction:column`,
779
- `.card-body` eine Flex-Spalte mit `gap:var(--s3)` und `flex:1`. Der `gap` ist damit der einzige
780
- vertikale Rhythmus der Karte, und die Kinder geben ihre Außenmargen ab (`.card-eyebrow`,
781
- `.card-cta-link`; `.card-body > .pill` nimmt die Eigenmarge der Pill zurück wie im
782
- Featured-Body). Das ist der Punkt: eine Außenmarge am Kind addiert sich im Flex-Layout zum
783
- `gap`, statt zu kollabieren, und muss dann in jedem Kontext einzeln zurückgenommen werden.
784
- `.card-featured` bleibt `display:block`, dort liegt der Body absolut.
785
- **Sichtbar ändert sich zweierlei, im Browser nachgemessen.** In der Featured-Card rückt der CTA
786
- von 24 px auf 16 px an die Zeile darüber und liegt damit im selben Rhythmus wie der übrige Body
787
- (`gap:var(--s4)`); vorher kamen 8 px Marge dazu. Und in den sieben Anatomie-Demo-Karten, die
788
- ihren Body nie inline geflext hatten, stehen Eyebrow, Titel und Text jetzt mit 12/12 px statt
789
- 8/0 px, die Karte wächst dadurch von 404 px auf 420 px. Alles andere bleibt gleich: die
790
- Listing-Cards, deren Body vorher inline geflext war, rendern unverändert (12 px zwischen den
791
- Kindern, gepinnte CTAs mit 20 px Padding-Luft an der Unterkante), das Bildverhältnis der
792
- Featured-Card bleibt 1,778, kein horizontaler Overflow.
793
- **Eine Ausnahme gehört zur Regel:** `.ep-feature-body` hat mit Absicht nur `gap:var(--s1)`, damit
794
- Titel und Text als Einheit gelesen werden. Für den CTA ist das zu eng, deshalb trägt der
795
- Container die Ausnahme (`.ep-feature-body > .card-cta-link{margin-top:var(--s2)}`) und die 13
796
- Feature-CTAs stehen unverändert bei 12 px. Genau so gehört es: nicht das Kind bringt die Marge
797
- überall mit, sondern der Container, der sie braucht, gibt sie seinem Kind.
798
- In den Mockups sind alle 43 + 41 + 41 + 39 Inline-Deklarationen entfernt; geblieben sind vier
799
- reine `gap`-Overrides (`--s4`, `--s6`), also Abstands-Entscheidungen, kein neu deklariertes
800
- Layout.
801
- `CONTRIBUTING.md` § 7 hält beide Regeln fest, an denen das hing: der Abstand gehört dem
802
- Container, nicht dem Kind, und eine Klasse trägt das Layout, das ihr eigenes Rezept voraussetzt.
803
- Prüfstein für künftige Bauteile: das Rezept aus der Doku muss allein mit seinen Klassen so
804
- aussehen, wie die Doku es zeigt.
805
- - **Die Pill wurde in Flex-Spalten zum Balken.** `.pill` ist `inline-block`, wird als Flex-Item
806
- aber zu `block` blockifiziert, und ein Item mit auto-Cross-Size zieht `align-items:stretch` auf
807
- die volle Spaltenbreite. Im `.card-featured-body` (Flex-Spalte, `gap:var(--s4)`) heißt das:
808
- gemessen bei 1280 px Kartenbreite 448 px Pill-Breite statt 146 px, also exakt die Inhaltsbreite
809
- des Bodys (40 % von 1280 px minus zweimal `--s8`). Dazu addierte sich die Eigenmarge der Pill
810
- (`margin-bottom:var(--s5)`, gedacht für den normalen Fluss über einer Überschrift) zum `gap` des
811
- Bodys, statt zu kollabieren: 36 px unter der Pill statt 16 px, wodurch sich der Meta-Block vom
812
- Bereichs-Signal löste, mit dem er als Meta-Header zusammengehört.
813
- Die Doku-Mockups zeigten den Fehler nicht, weil an jeder der 46 Pill-Instanzen
814
- `style="width:fit-content;margin-bottom:0"` stand. Im Copy-Paste-Rezept der Featured-Card fehlte
815
- dieser Override. Wer das Rezept übernahm, bekam den Balken.
816
- Jetzt bringt `.pill` selbst `width:fit-content` mit (im normalen Fluss ein No-Op, ein
817
- `inline-block` schrumpft dort ohnehin auf seinen Inhalt; deckt Flex und Grid in einem ab, weil
818
- eine definierte Cross-Size `stretch` in beiden schlägt), und `.card-featured-body > .pill` nimmt
819
- die Eigenmarge dort zurück, wo der Body den Abstand als `gap` selbst trägt. In den Mockups sind
820
- alle 46 `width:fit-content` entfernt, die 7 Featured-Pills brauchen gar kein `style`-Attribut
821
- mehr. Auch die 39 Pills in einem `.card-body` sind ihren Override los, seit `.card-body` seine
822
- Flex-Spalte selbst trägt (siehe den Eintrag darüber). Doku: die Pill-Sektion, das
823
- Featured-Rezept und die technischen Regeln der Veranstaltungs-Card nennen jetzt, was die Klasse
824
- leistet und was im Markup nichts zu suchen hat.
825
- - **Kartenraender waren im Light praktisch unsichtbar und untereinander uneinheitlich.** Die
826
- generische `.card` trug `--bd` (n-100), Testimonial- und Stat-Karte `--bd-strong` (n-200) plus
827
- einen 4-px-Bereichsakzent. Gemessen im Light: 1,18:1 gegen Weiss und 1,10:1 gegen eine
828
- `n-50`-Sektion, gegenueber 1,53:1 bei den anderen. Alle Karten haben 1 px, der Unterschied kam
829
- also aus Farbe und Akzent, nicht aus der Strichstaerke. Im Dark war beides nie ein Problem
830
- (2,99:1 und 4,21:1). Zwei Ursachen: es gab keine Regel, wann welches Token gilt (§4 nannte
831
- beide ohne Kriterium), und `.ep-card`, die Karte der Beispielseiten, nutzte laengst
832
- `--bd-strong`, waehrend die dokumentierte `.card` bei `--bd` blieb.
833
- Jetzt sind es **zwei Rollen statt zwei Staerken**: `--bd` ist die Trennlinie **innerhalb** eines
834
- Bauteils (Card-Footer, Caption-Kante, Tabellenzeile), `--bd-strong` die **Aussenkante** einer
835
- Flaeche. 18 Aussenkanten in `components.css`/`base.css` und 61 Demo-Kaesten in der Doku sind
836
- umgestellt; Beispielcode blieb unberuehrt. Ausgenommen bleiben Bedienelemente: Formularfelder
837
- und Buttons haben `--field-border` (4,13:1, WCAG 1.4.11), Pills und Chips ihren eigenen Rand,
838
- deshalb behalten `.a11y-mode` und `.logo-carousel-pause` ihr `--bd`.
839
- Dazu traegt `--bd-strong-c` im Light jetzt `n-300` statt `n-200`: **2,25:1** gegen Weiss und
840
- **2,09:1** gegen eine `n-50`-Sektion. Der Dark-Wert bleibt unangetastet. Damit stimmt auch eine
841
- Begruendung wieder, die das System schon fuehrte: §5 leitet den Sektions-Rhythmus daraus ab,
842
- dass die weisse Karte auf weisser Sektion "ueber ihren Rand" liest, nannte im selben Satz aber
843
- 1,10:1 fuer Light. Die drei Kommentare in `dark-mode.css`, die 1,10:1 als ausreichend
844
- bezeichneten, und der Wert in der Stoerer-Tabelle sind mitgezogen. Der 4-px-Bereichsakzent
845
- bleibt Unterschied: die generische Karte zeigt ihren Bereich ueber die getoente Medienflaeche,
846
- Testimonial und Stat-Karte haben keine, dort traegt die Oberkante die Bereichsfarbe.
847
-
848
- - **Zwei tote Nav-Links in der Barrierefreiheits-Sektion.** „Tastaturnavigation“
849
- (`#gt-a11y-keyboard`) und „Touch Targets“ (`#gt-a11y-touch`) standen in der Sub-Navigation,
850
- ohne dass es die Ziele gab, während die Einleitung derselben Sektion beide Themen ankündigt.
851
- Beide Blöcke sind jetzt geschrieben, siehe „Added“.
852
- - **Fünf Überschriften waren über die Sidebar nicht erreichbar.** „Editoriale Kennzahl-Zeile“,
853
- „Kontrastverhältnisse · Dark Mode“ und die beiden Verwendungs-Blöcke in Slider & Carousel
854
- hatten keinen Nav-Eintrag. Weil diese Sektion zwei Bauteile dokumentiert, heißen ihre
855
- Verwendungs-Blöcke jetzt „Bild-Carousel · Verwendung“ und „Kundenlogo-Karussell · Verwendung“,
856
- nach dem schon beim Störer verwendeten Muster. `gt-colors-semantik` war dagegen kein
857
- Gruppen-Titel, sondern das Label einer Spalte im Verwendungs-Block, angesprungen von einem
858
- Querverweis; es heißt jetzt `colors-semantik`, damit `gt-` genau die Überschriften mit
859
- Nav-Eintrag bezeichnet.
860
- - **Übersprungene Heading-Ebene in der Token-Sektion (WCAG 1.3.1).** `sec-tokens` ging von
861
- `h1.sec-title` direkt auf nackte `h3` und war deshalb die einzige Grundlagen-Sektion ohne
862
- Sub-Navigation, zwei Symptome derselben Ursache. Die fünf Token-Blöcke tragen ihre Überschrift
863
- jetzt als `h2.group-title` und erscheinen in der Sidebar; `.token-section h3` entfällt.
864
- - **Fünf Anker trugen das Präfix einer fremden Sektion.** Reste früherer Umzüge:
865
- `gt-nav-hero-image`, `gt-nav-hero-substrip` und `gt-carousel-hero` lagen in `sec-hero`,
866
- `gt-logo-carousel*` in `sec-slider`. Alle sieben IDs der beiden Sektionen folgen jetzt ihrer
867
- eigenen Sektion, samt der Querverweise im Fließtext.
868
- - **Inhaltsverzeichnis von `docs/GETTING-STARTED.md` unvollständig.** Es listete 8 Kapitel bei 9
869
- vorhandenen: „Einheiten & medienübergreifende Nutzung“ war als §7 eingeschoben worden, ohne das
870
- Verzeichnis nachzuziehen, wodurch die letzten zwei Einträge auf `#7-icons-nutzen` und
871
- `#8-frameworks` zeigten statt auf `#8-…` und `#9-…`. Alle 9 Anker lösen wieder auf.
872
- - **`README.md` beschrieb eine Gliederung, die es nicht mehr gibt.** Statt „32 Sektionen:
873
- Foundations (…)“ jetzt 35 Sektionen in den sechs aktuellen Gruppen.
874
- - **Störer bei 200 % Textgröße: Maße von px auf rem (WCAG 1.4.4).** Die Container-Query-Schwelle
875
- stand als `1025px` da und skalierte damit nicht mit der Browser-Standardschriftgröße. Bei
876
- verdoppelter Wurzel-Schrift wuchs die Kachel von 126 auf 290 px, das Overlay blieb aber an und
877
- ragte gemessen 96 px in die Sub-Sektion. Jetzt `64.0625rem`: bei 150 % und 200 % verlangt die
878
- Schwelle mehr Hero-Breite als vorhanden, das Set fällt also in den Block-Zweig unter dem Hero,
879
- statt zu überlappen. Ebenso Kachelbreite (`22.5rem`) und Grid-Minimum (`16.25rem`).
880
- Nebenbefund, der **nicht** vom Störer kommt: bei 200 % läuft die Doku-Seite horizontal über
881
- (1498 statt 1440 px). Mit und ohne Störer identisch gemessen, kein Element der Beispielseite ragt
882
- über den Viewport; das ist die Doku-Shell (252-px-Sidebar plus Content).
883
- - **Reflow bei 320 px strukturell abgesichert (WCAG 1.4.10).** Das Auto-Fit-Grid hatte einen
884
- starren Boden von `16.25rem`; bei 320 px Viewport ist der Grund nur 256 px breit, die Spalte ragte
885
- also 4 px in die Polsterung. Es entstand kein horizontaler Überlauf, weil das Padding es auffing —
886
- das hielt aber nur zufällig. Jetzt `minmax(min(16.25rem,100%),1fr)`, die Spalte weicht auf 100 %
887
- aus statt den Grund zu sprengen. Dazu mobil (≤ 768 px) derselbe Innenabstand wie `.ep-section`
888
- (`--s6` statt `--s8`): im Block-Zweig steht das Set in der Sektionslogik der Seite und folgt deren
889
- Rhythmus. Gemessen bei 320/360/400/768 px: kein Überlauf, ein bzw. zwei Spalten.
890
- - **`.stoerer` in die `forced-colors`-Rahmenliste aufgenommen.** Im Windows-Kontrastmodus entfernt
891
- das OS `box-shadow`; `base.css` stellt für `.card` und `.card-elevated` deshalb `2px solid
892
- CanvasText` her. Der Störer fehlte in der Liste und behielt nur seinen 1-px-Rand — genau im
893
- schwierigsten Fall, nämlich über einem Foto und ohne den `--e1`, auf den er sich sonst stützt.
894
- Der Fokus-Ring war nicht betroffen, die globale `:focus-visible`-Regel ersetzt ihn dort schon
895
- durch `outline: 3px solid Highlight` (in beiden Modi nachgemessen).
896
-
897
- ### Added
898
- - **Die Brand-Logos werden jetzt ausgeliefert: `assets/brand/`.** Wer das Paket eingebunden hat,
899
- bekam kein Logo. Die drei Wortmarken lagen unter `docs/assets/images/`, und `docs/` steht nicht in
900
- `package.json` → `files`; einen `exports`-Eintrag für Assets gab es ebenfalls nicht. Dokumentiert
901
- war das Logo dagegen vollständig (Doku-Sektion **Marke → Logo**), es fehlte allein der Weg zum
902
- Developer. Zwei Nebenfolgen derselben Ablage: die Doku nannte in der Prosa den Pfad
903
- `images/logo-conciso{,-light,-dark}.svg`, real war es `assets/images/…`, und Storybook mountete
904
- für **9,7 KB** Logo den kompletten **164 MB** großen Demo-Bilderordner nach `/conciso/images`.
905
- Die drei SVGs liegen jetzt unter `assets/brand/` und sind über `files` und
906
- `exports["./assets/brand/*"]` Teil des Pakets, ansprechbar als
907
- `@conciso/design-system/assets/brand/logo-conciso.svg`. Umgestellt sind 59 `src`-Attribute in
908
- `docs/index.html` (Sektionen Logo, Navigation, Beispielseiten) auf `../assets/brand/`, die falsche
909
- Prosa-Angabe, der Storybook-Mount (jetzt `assets/brand` → `/conciso/brand`, 164 MB auf 9,7 KB) samt
910
- acht Story-Referenzen und das Vorschau-Skript, das die Root-Assets mitkopiert und `../assets/` wie
911
- bisher `../css/` auf Geschwister-Ebene umschreibt. `assets/brand/README.md` beschreibt den Einbau,
912
- verbindlich für Größen, Schutzraum und Verwendung bleibt die Doku-Sektion.
913
- `logo-conciso.svg` trug noch XML-Prolog, `DOCTYPE` und drei ungenutzte Namespaces des Export-Tools,
914
- die beiden anderen Varianten nicht. Sie sind entfernt, die Pfaddaten sind byte-identisch geblieben
915
- (3609 auf 3355 Byte). Zwei der Dateien hatten fälschlich das Executable-Bit, jetzt `644`.
916
- `width` und `height` bleiben an allen drei SVGs, anders als es CONTRIBUTING § 10 für
917
- `icons/source/` vorschreibt: Icons werden inline eingebettet, Logos per `<img>`, und dort liefern
918
- genau diese Attribute das intrinsische Seitenverhältnis und verhindern Layout-Shift. Die Begründung
919
- steht in `assets/brand/README.md`, damit sie niemand gegen die Icon-Regel wegräumt.
920
- Bewusst **nicht** Teil davon: ein Favicon- oder App-Icon-Set (die Wortmarke ist bei 7,6 zu 1 als
921
- Favicon unbrauchbar, Quelle wäre `icons/source/co-mark.svg`), eine `currentColor`-Variante (der
922
- CSS-Swap über `.logo-themed-default`/`-light` bleibt der Weg) und die Auslagerung der Demo-Fotos,
923
- die weiter unter **Geplant** steht. Die Fremdmarken `logo-youtube-light.svg` und
924
- `logo-linkedin-light.png` bleiben bei den Demo-Bildern, sie sind keine Conciso-Assets.
925
- - **`.card-featured`: Featured-Card mit dem Bildverhältnis der Listing-Cards.** Die Featured-Card
926
- hatte kein definiertes Bildverhältnis. Die Bildspalte war ein `<div>` mit `min-height` und einem
927
- CSS-`background-image`, ihre Höhe ergab sich aus dem Text daneben. Gemessen bei 1440 px Viewport:
928
- **1,62**, während `.card-media` im Listing-Grid **1,778** (16/9) liefert. Redaktion musste damit
929
- pro Beitrag zwei Zuschnitte pflegen, einen fürs Featured und einen fürs Grid.
930
- Die Bildspalte trägt jetzt `.card-media` und damit dasselbe `aspect-ratio`, der Bildausschnitt
931
- sitzt als `object-position` am `<img>` statt als `background-position` am Container. Ein Bild in
932
- einem Zuschnitt passt damit in beide Slots.
933
- Damit das Verhältnis unabhängig von der Textlänge hält, bestimmt das **Bild** die Kartenhöhe:
934
- `.card-featured-body` liegt absolut und trägt nichts zur Zeilenhöhe bei. Als normale Spalte zieht
935
- seine Inhaltshöhe die Zeile auf und streckt das Bild mit. Ein `aspect-ratio` auf der Karte genügt
936
- dafür nicht, es ist eine Wunschgröße und verliert gegen die Inhaltshöhe (gemessen 1,50).
937
- Spaltenteilung 60/40 statt 50/50, weil ein 16/9-Bild auf halber Kartenbreite zu flach für den
938
- Content-Block ist: bei 50 % schrumpfte der Lead auf null Zeilen.
939
- - **Drei Stufen der Featured-Card, an der Kartenbreite statt am Viewport.** Alle Stufen hängen per
940
- `@container` an der Karte (CONTRIBUTING § 7). Im Doku-Mockup ist die Karte bei 1180 px Viewport nur
941
- 814 px breit, bei 1024 px dagegen 958 px, weil die Doku-Sidebar unterschiedlich greift. Eine
942
- Viewport-Regel träfe dort das Falsche.
943
- Über **1000 px** Kartenbreite: Bild 60 %, Padding `s6 s8`, Lead drei Zeilen.
944
- **900 bis 1000 px** (Tablet-Landscape): nebeneinander bleiben, aber verdichtet. Bild 65 %, Padding
945
- `s4 s6`, Lead zwei Zeilen. Bei 16/9 heißt breiteres Bild auch höheres Bild, und diese Höhe
946
- braucht der Content-Block. Vorher wurde hier gestapelt: das Bild wurde full width und 539 px hoch,
947
- die Karte 807 px, auf einem 1024×768-iPad also höher als der Bildschirm. Jetzt 350 px.
948
- Unter **900 px**: gestapelt wie die Grid-Cards, und das Lead-Kürzen wird zurückgenommen, weil
949
- ohne feste Höhe kein Grund besteht, eine Zeile zu opfern. Tiefer nebeneinander zu bleiben lohnt
950
- nicht, die Textspalte fiele unter 250 px, rund 30 Zeichen pro Zeile.
951
- - **Kürzen statt Verhältnis brechen: der Vorrang steht fest.** `.card-text` bringt sein `line-clamp`
952
- mit und gibt als schrumpfendes Flex-Item zuerst nach, bevor Pill, Meta-Strip, Titel oder CTA
953
- wegfallen. Dazu kappt `.card-title-hero` **innerhalb** der Featured-Card auf drei Zeilen: ohne
954
- diesen Riegel frisst ein langer Titel die Höhe, die der Lead braucht, und schiebt danach Pill und
955
- CTA aus der Karte (gemessen mit einem 18-Wort-Titel). Global bleibt `.card-title-hero`
956
- unbeschnitten. Rest-Fall, bewusst so gelassen: schöpft ein Titel den Riegel voll aus, wird die
957
- letzte Lead-Zeile angeschnitten statt mit Auslassungspunkten zu enden. Dann muss etwas nachgeben,
958
- und eine halbe Lead-Zeile ist der geringste Schaden; die Karte bleibt in jedem Fall geschlossen.
959
- Ein kleinerer `gap` verschiebt den Fall nur (1,98 statt 1,76 Zeilen) und löst ihn nicht.
960
- - **Tastaturnavigation und Touch Targets dokumentiert.** Beide Blöcke fehlten in einem System mit
961
- AA-Ziel. Tastaturnavigation behandelt Fokusreihenfolge aus dem Markup (keine positiven
962
- `tabindex`), Sprungziele mit `tabindex="-1"`, den Skip-Link, die Tastaturfalle, verdeckten Fokus
963
- und ein gemeinsames Tastenset für neue Bauteile (2.1.1, 2.1.2, 2.4.3, 2.4.7, 2.4.11), dazu eine
964
- Tabelle mit der tatsächlichen Belegung aller Bauteile aus `main.js`. Bei den Zielgrößen sind die
965
- zwei Kriterien getrennt benannt, weil sie nicht dasselbe verlangen: 2.5.8 fordert auf AA 24 px,
966
- 2.5.5 auf AAA 44 px, und das System zielt auf 44 px. Die zwei Bauteile darunter (Segmented
967
- Control, Area Tabs, je 40 px) stehen als bewusste Abweichung dabei, statt unerwähnt zu bleiben.
968
- 2.4.11 und 2.5.8 sind als Kriterien aus WCAG 2.2 gekennzeichnet, da die Sektion 2.1 nennt und es
969
- dort auf AA keine Zielgrößen-Anforderung gibt.
970
- - **Sub-Navigation für Design Tokens und Beispielseiten.** Beide Sektionen waren in der Sidebar
971
- ohne Tiefe. Die Beispielseiten sind der größte Teil der Doku, ihre Gliederung lag allein in der
972
- Tab-Leiste. Die sechs Gruppen stehen jetzt in der Sidebar und ein Eintrag **öffnet** die erste
973
- Beispielseite seiner Gruppe: gemessen bei 1440 px liegen fünf der sechs Gruppen-Labels in
974
- derselben Grid-Zeile von `.ep-tabs` und die Leiste ist 328 px hoch, ein reiner Anker führte also
975
- fünfmal an dieselbe Stelle. Umgesetzt über das vorhandene `activateExamplePage`; der `href` zeigt
976
- auf den ersten Tab der Gruppe und bleibt ohne JS ein gültiges Sprungziel.
977
- - **`CONTRIBUTING.md` §11 „Doku-Struktur“.** Für die Gliederung der Doku-Site gab es keine
978
- geschriebene Regel, nur einen Halbsatz in §10 Schritt 4, der drei Inhaltsblöcke nennt. Der neue
979
- Abschnitt hält fest: die sechs Gruppen und was hineingehört, Nav-Eintrag als Pflicht, das
980
- Anker-Schema `gt-<sektion>-<thema>` und wofür das Präfix reserviert ist, „Aufbau“ zuerst und
981
- „Verwendung“ zuletzt samt der Form `<Bauteil> · Verwendung`, die Bereichsreihenfolge, Nav-Label
982
- gleich Überschrift oder deren Anfang, ein Trennzeichen pro Aufgabe, die Sprachregel und die
983
- Heading-Ebenen samt der einen dokumentierten Ausnahme. Dazu zwei Einzeiler zum Selbstprüfen und
984
- eine Zeile in der PR-Checkliste.
985
-
986
- ### Changed
987
- - **Alle sieben Featured-Cards auf `.card-featured` umgestellt.** Wissen-Übersicht (drei
988
- bereichsspezifische Varianten), Veranstaltungs-Übersicht (drei) und das Featured auf der Landing.
989
- Damit liegt Layout, Spaltenteilung und das Kürzen in der Klasse statt im `style`-Attribut, und
990
- die alte `min-height`-Konstruktion mit `background-image` ist restlos aus dem Repo. Geprüft über
991
- alle drei Beispielseiten bei zehn Kartenbreiten von 1200 bis 375 px, Light und Dark: 16/9 hält
992
- durchgängig, Pill und CTA bleiben sichtbar, der Lead endet auf Zeilenkante.
993
- - **Doku der Featured-Card beschrieb das Bild als 1:1 und die Geometrie als `min-height`-Spalte.**
994
- Beides traf schon vor dieser Änderung nicht zu, gemessen waren es 1,62 und eine Spalte ohne
995
- definiertes Verhältnis. Prosa, Vergleichstabelle und Code-Block sind auf 16:9, `.card-media` und
996
- die drei Container-Stufen gezogen. In der Vergleichstabelle stand für den Lead außerdem noch
997
- 14 px, `.card-text` trägt Body-md mit 16 px.
998
- - **Reihenfolge und Benennung der Doku-Navigation.** „Verwendung“ schließt eine Sektion ab, bei
999
- Elevation und Tabelle stand danach noch ein Block. Icons listete die Bereiche als co/ki/wo/es,
1000
- kanonisch und wie bei Buttons ist co/ki/es/wo. Der Eintrag Design Tokens sitzt jetzt hinter
1001
- Elevation statt zwischen Icons und Barrierefreiheit, also hinter den vier Themen, deren Werte er
1002
- festhält. Bei diesen Umzügen ist die Zeilenmenge der Datei unverändert, nur die Reihenfolge.
1003
- Benennung: Gruppen- und Konzeptnamen deutsch (Foundations → Grundlagen, Components →
1004
- Komponenten, Page-Patterns → Seitenmuster, Colors → Farben, Typography → Typografie, CSS Design
1005
- Tokens → Design Tokens, WCAG 2.1 AA & Accessibility → Barrierefreiheit, Hero-Patterns → Hero),
1006
- etablierte Bauteil- und Token-Namen unverändert. „Brand Areas“ bleibt: der Begriff steht 34 Mal
1007
- in der Doku-Prosa und je zwei Mal in CONTRIBUTING und CHANGELOG, nur die Sektion umzubenennen
1008
- hieße ein Ding, zwei Wörter. Nav-Label ist jetzt überall die Überschrift oder ihr Anfang
1009
- (vorher wichen 10 von 165 ab, am weitesten „Verschachtelung“ für „Optional: Sub-Hierarchie per
1010
- `.footer-subtitle`“, ohne ein gemeinsames Wort). Für „Name plus Zusatz“ gilt ein Trennzeichen:
1011
- `·` bestimmt näher, `:` steht vor einem Satz, Klammern tragen einen kurzen Einschub.
1012
- - **Domänenwort für Veranstaltungen systemweit vereinheitlicht.** Das Sektions-Label „Treffen“ auf
1013
- der Startseite und der Topnav-Eintrag „Events“ heißen jetzt beide „Veranstaltungen“. Für dieselbe Domäne
1014
- standen auf einer Seite drei Wörter: „Events“ (Topnav), „Treffen“ (Sektions-Label) und „Alle
1015
- Veranstaltungen ansehen“ (Link am Sektionsende); dazu kommt die Übersichtsseite, die von
1016
- Veranstaltungen spricht. Mit dem Störer verlinken zwei Elemente derselben Seite auf dasselbe Ziel,
1017
- und dann sind zwei verschiedene Namen nicht mehr nur uneinheitlich: für Screenreader liest sich das
1018
- wie zwei verschiedene Inhalte. „Veranstaltungen“ ist das Wort, das Übersichtsseite, Detailseite und
1019
- der Link am Sektionsende schon tragen. Angeglichen sind 27 Topnav-Einträge über alle
1020
- Beispielseiten, das Sektions-Label, die Karte und der CTA auf der Unternehmens-Übersicht, die
1021
- Anmeldungs-Einwilligung, der Hero-Eyebrow „Fokusevent“ (jetzt „Fokusveranstaltung“) sowie die
1022
- Doku-Prosa inklusive des Komponenten-Namens „Event-Card“ (jetzt „Veranstaltungs-Card“). Im Wort
1023
- „Event“ verbleiben nur technische Bezeichner (`gt-event-*`, `sec-events`, `pointer-events`,
1024
- `data-event-meta`, Bild-Dateinamen) und die internen Team-Aktivitäten auf der Arbeitgeber-Seite
1025
- („Teamevents“, „Kochevents“): das sind andere Dinge, und zwei Dinge dürfen zwei Wörter haben.
1026
- Kontrolliert: 0 Textüberläufe in vier Breiten, das Wort ist mehr als doppelt so lang wie „Events“
1027
- und passt in das 240 px breite Dropdown-Panel.
1028
- CONTRIBUTING § 6 hält die Regel fest: ein Domänenwort pro Domäne über Nav, Sektions-Label und
1029
- Verweis-Komponenten. Dazu die Notiz, dass die Eyebrow-Form (`--ty-label-xs` + uppercase +
1030
- `--co-ink`) mit vier Rollen voll belegt ist und keine fünfte Bedeutung mehr tragen soll.
1031
- - **Paletten-Beschriftung auf einen neutralen Streifen unter dem Farbfeld.** Sie saß auf der Farbe,
1032
- und dort ist der Kontrast nicht garantierbar: bei **7 von 100** Stufen liegt die Leuchtdichte so
1033
- in der Mitte, dass weder weiße noch dunkle Schrift 4,5:1 erreicht (auf `ki-700` schafft selbst
1034
- reines Schwarz nur 4,82:1). Die Zwischenlösung war eine Plakette auf genau diesen sieben, was als
1035
- Ungleichheit auffiel und wie ein Fehler las. Jetzt tragen alle Beschriftungen `--tx-secondary` auf
1036
- `--bg-surface`: **6,28:1 in beiden Modi**, das Farbfeld bleibt unverdeckt, und die 100 inline
1037
- gesetzten Label-Farben im Markup sind überflüssig geworden und entfernt. Der abzulesende Hexwert
1038
- ist Information, nicht Dekoration, und hat jetzt einen Grund, auf dem er trägt.
1039
- Layout dazu: die Rundung sitzt an den Farbfeldern statt an der Reihe (die Reihe klippte sonst den
1040
- Streifen an den Außenkanten ab), der Streifen hat Luft nach oben statt am Farbfeld zu kleben, die
1041
- Beschriftung bricht nicht mehr um (ein Umbruch bei „800 T“ schob das Farbfeld dieser Spalte nach
1042
- oben und ergab eine Stufe in der Reihe), und das Farbfeld behält seine 60 px Höhe. Unter 1024 px
1043
- entfällt der Hexwert, dort ist eine Spalte zu schmal; er bleibt im `title` und per Klick kopierbar.
1044
- Und die Reihe ist jetzt eine Karte mit Haarlinie: ohne Rahmen verlor sie im Light ihre Kante, weil
1045
- die hellen Stufen und der weiße Beschriftungs-Streifen beide fast die Farbe der Seite haben
1046
- (`#E0F7F7` gegen Weiß sind 1,12:1). Dazu eine Linie zwischen Farbfeld und Beschriftung, aus
1047
- demselben Grund. Im Dark trennte sich der Streifen schon vorher von der Seite, dort ist beides
1048
- nur konsequent.
1049
- - **Pill und Bereichs-Badge folgen im Light dem Kachel-Rezept.** Zarter `--XX-50`-Tint mit
1050
- kräftiger dunkler Schrift (7,5 bis 12,3:1) statt kräftiger Füllung. Begründung wie bei den
1051
- Kacheln: WCAG verlangt für diese Fläche nichts, die Aussage steht im Label, und darüber
1052
- entscheidet das Markenrad. Auf dem zarten Grund wirkt die Schrift kräftiger, weil sie nicht mit
1053
- der Fläche konkurriert. **Im Dark bleibt die gehobene Füllung**, weil sich dort das Verhältnis
1054
- umdreht: helle Schrift auf dunklem Tint, und bei fast gleicher Helligkeit wie die Karte hält
1055
- nichts mehr die Fläche zusammen (1,54 bis 1,55:1 zur Karte, 12,5 bis 12,7 L*). Gleiche Rolle,
1056
- zwei Werte, wie bei `--XX-band`.
1057
- Das Gate prüft die 1,3:1-und-10-L*-Regel damit nur noch für **Status**-Füllungen, bei denen die
1058
- Farbfläche in dichten Kontexten der schnelle Hinweis ist, bevor das Label gelesen wird. Für
1059
- Label- und Dekor-Flächen bleibt es beim Text, und der wird ohnehin geprüft.
1060
- - **Dekorative Flächen dürfen ruhig bleiben: Icon-Kacheln zurück auf den zarten Tint.** Beim
1061
- Anheben der Füllungen waren die 48-px-Kacheln mitgelaufen, obwohl für sie nichts davon gilt: WCAG
1062
- verlangt für eine dekorative Fläche keinen Kontrast (kein Bedienelement, kein bedeutungstragendes
1063
- Grafikobjekt, das Glyph ist `aria-hidden` und wiederholt das Eyebrow daneben), und großflächig
1064
- gesättigte Farbe widerspricht dem Markenwert „Ruhig“. Kacheln und Timeline-Marker tragen jetzt
1065
- wieder `--XX-50`, ohne Rand; die Erkennbarkeit trägt das Glyph mit 7,5 bis 11,4:1 im Light und
1066
- 6,9 bis 8,6:1 im Dark, also mehr als auf der kräftigen Füllung (dort 4,7 bis 7,5:1).
1067
- Dazu die Rollen sauber getrennt, damit die Entscheidung am Token-Namen hängt: **`--XX-50`** ruhige
1068
- dekorative Fläche (in beiden Modi dasselbe Versprechen; im Dark stehen dort wieder die ruhigen
1069
- Werte) · **`--XX-fill`** textführendes Bauteil, muss lesen (im Dark eigene, gehobene Werte statt
1070
- einer Referenz auf `-50`) · **`--XX-band`** Sektionsfläche · **`--XX-ink`** farbiger Text.
1071
- Das Gate prüft die Hausregel entsprechend nur für textführende Füllungen; warum die Kacheln nicht
1072
- drin sind, steht im Skript.
1073
- CONTRIBUTING § 3 führt die Entscheidung jetzt zweistufig: WCAG ist Pflicht und gilt für den
1074
- Inhalt, darüber hinaus entscheidet das Markenrad und nicht der Kontrastrechner.
1075
- - **Getönte Füllungen im Dark um 4 L\*-Punkte gehoben, und das Gate prüft jetzt zwei Werte.** Die
1076
- vier Bereichs-Füllungen erfüllten mit 1,33:1 den 1,3:1-Faustwert, hatten aber nur **8,3 L\***
1077
- Helligkeitsabstand zur Karte: der Unterschied lag fast vollständig in Farbton und Sättigung, und
1078
- eine satte Fläche auf gleicher Helligkeit liest als dunkler Fleck statt als hellere Stufe. Am
1079
- deutlichsten bei ES, weil Blau bei gleichem L\* am dunkelsten wirkt; genau dort ist es aufgefallen
1080
- (Pill der Veranstaltungskarte). Neu in Lab gerechnet, nur L angehoben, Farbton und Sättigung
1081
- unverändert: `--co-50` `#1F5651`, `--ki-50` `#43531C`, `--wo-50` `#205828`, `--es-50` `#34469D`.
1082
- Fläche jetzt 1,54 bis 1,55:1 zur Karte und 2,07 bis 2,09:1 zur Seite, ΔL\* 12,5 bis 12,7.
1083
- Dasselbe für die Status-Füllungen, die das neue Kriterium mit aufdeckte (ΔL\* 8,2 bis 8,4):
1084
- `--c-success-bg` `#1F573B`, `--c-warning-bg` `#5F4B1E`, `--c-error-bg` `#882725`,
1085
- `--badge-neu-bg` `#404F5D`. Beim Fehlerton bewusst bei ΔL\* 11 gestoppt, weil der Text darauf
1086
- sonst unter AA fällt (jetzt 4,74:1).
1087
- **Das Gate prüft Füllungen ab jetzt gegen 1,3:1 UND 10 L\***, sonst wäre genau dieser Fall wieder
1088
- durchgerutscht. Es hat dabei drei Folgefehler gefunden: Inline-Code (`.token`) trug im Dark
1089
- `co-300` und fiel auf der helleren Füllung auf 4,48:1, ebenso die Grid-Spalten-Labels; beide
1090
- laufen jetzt über `--co-ink`. Vier Code-Chips trugen zusätzlich eine semantische Inline-Farbe
1091
- (`--c-error` und Verwandte) und lagen bei 4,46:1; die Farbe ist raus, den Zweck erklärt die Zeile
1092
- daneben. Und ein CSS-Kommentar mit `a*/b*` hat beim Schreiben den Kommentar vorzeitig beendet und
1093
- den halben Dark-Token-Block gekippt: 1.705 gemeldete Verstöße, in einem Lauf gefunden.
1094
- - **Alle Kontrast-Verstöße der Doku behoben: 183 auf 0, in beiden Modi.** Ausgangsstand nach dem
1095
- Bau des Gates: Light 49 Text / 101 Füllungen / 6 Rahmen, Dark 20 / 0 / 0. Neun Gruppen, jede mit
1096
- einer Ursache:
1097
- - **Farbiger Text ohne Theme-Wechsel (17 Stellen).** `co-600` als Textfarbe trug auf Weiß nur
1098
- 3,29:1. Dabei fiel auf, dass das System `--XX-ink` (Light `-700`/`-800`, Dark `-200`/`-100`)
1099
- nur für KI hatte. Die Familie ist jetzt vollständig: **`--co-ink`, `--es-ink`, `--wo-ink`**.
1100
- Ohne sie erzeugt jede Light-Korrektur einen Dark-Verstoß, was beim ersten Anlauf genau so
1101
- passiert ist (16 neue Dark-Befunde durch ein fest gesetztes `co-700`).
1102
- - **Getönte Füllungen (101).** 76 Icon-Kacheln, 13 Status- und neutrale Badges, 8 Timeline-Icons,
1103
- 4 Trend-Chips lagen zwischen 1,02 und 1,16:1 gegen ihren Grund. Kacheln und Timeline-Icons
1104
- nutzen jetzt `--XX-fill`, die Status-Tints sind auf Flächenwirkung angehoben
1105
- (`--c-success-bg` `#A9E5C8`, `--c-warning-bg` `#F5D08C`, `--c-error-bg` `#F5BFBF`, Fläche 1,43
1106
- bis 1,60:1), der neutrale Badge auf `n-200`. Die Tint-Texte wandern mit: `--c-warning` auf
1107
- `#785008` und `--c-error` auf `#9B2020`, damit sie auf der kräftigeren Füllung 4,84 bzw. 4,99:1
1108
- tragen; `--badge-*-text` verweist jetzt auf dieselben Token statt eigene Werte zu führen.
1109
- - **60 inline gesetzte Kachel-Tints** sind aus dem Markup in die Komponente gewandert
1110
- (`.ep-card-icon[data-area]`).
1111
- - **`--tx-muted` auf `#5A7171`** (vorher `#5E7676`): auf Weiß 4,85:1 ✓, aber auf `n-50` und
1112
- getönten Gründen nur 4,35 bis 4,51:1, also genau dort unter AA, wo leiser Text meist sitzt.
1113
- Jetzt 4,66 bis 5,20:1 und weiter klar heller als `--tx-secondary`.
1114
- - **Paletten-Beschriftungen (11).** Die Labels trugen `opacity:.75` auf der Swatch-Farbe, 2,53
1115
- bis 4,36:1, und genau dort steht der Hexwert zum Ablesen. Deckkraft raus; für die
1116
- Mittelton-Stufen, auf denen weder weißer noch dunkler Text 4,5:1 erreicht (auf `ki-700`
1117
- schafft selbst Schwarz nur 4,82:1), gibt es `.swatch.is-midtone` mit einer Plakette.
1118
- - **Halbtransparentes Weiß auf Bereichsbändern (4).** `.7` bis `.85` trugen 3,54 bis 4,46:1.
1119
- Jetzt deckend; die dokumentierte Konvention nannte `.85` und ist mit korrigiert.
1120
- - **Bedienelement-Rahmen (6).** Segmented Control und vier inline gestylte Suchfelder nutzten
1121
- `n-200` (1,53:1) statt `--field-border` (3,92:1), also unter der 3:1-Schwelle aus WCAG 1.4.11.
1122
- - **Terminal-Variante des Code-Blocks (3).** Kopier-Button und Ausgabe-Zeilen trugen 3,09 bzw.
1123
- 4,20:1 auf dem dunklen Grund.
1124
- - **Slider ohne Bereichsklasse (1).** Fiel im Dark auf den `co-700`-Fallback und stand mit
1125
- 2,36:1 dunkel auf dunkel; die vier Bereichsvarianten waren gesetzt, der Grundfall nicht.
1126
- - **Kontrast-Gate `npm run check:contrast`.** Rendert `docs/index.html` in beiden Modi in Chromium,
1127
- löst für jeden Textknoten den effektiven Grund über die Elternkette auf (halbtransparente
1128
- Schichten werden aufeinander komponiert) und prüft Text (4,5:1 bzw. 3:1), getönte
1129
- Bauteil-Füllungen (Hausregel 1,3:1) und Bedienelement-Rahmen (3:1). Es misst die **fertige Kette**
1130
- statt des CSS und findet damit auch inline gesetzte Farben, die kein Token-Check sieht. Läuft in
1131
- `storybook-angular.yml`, weil es einen Browser braucht; das Wurzelprojekt bleibt ohne
1132
- Dependencies. Genau **eine** dokumentierte Ausnahme: Swatches, die ein Farbpaar als Inhalt zeigen.
1133
- Text über Fotos und Verläufen wird gezählt, nicht gewertet (braucht Pixelmessung).
1134
- Absicherung als **Ratsche**: eine Zahl pro Kategorie im Skript, Fehlschlag wenn sie steigt und
1135
- ebenso wenn sie sinkt, ohne nachgezogen zu werden. Erster Stand, Light/Dark:
1136
- **56/20** Text, **101/0** Füllungen, **6/0** Rahmen. Ziel überall 0.
1137
- Nebenbefund beim Bau: die ersten Messläufe lasen Zwischenwerte der Theme-Animation, die Zahlen
1138
- wanderten je nach Wartezeit. Das Gate schaltet Übergänge vor der Messung ab, zwei Läufe
1139
- hintereinander liefern jetzt identische Zahlen.
1140
- - **Pill- und Badge-Füllungen lesen im Light wieder als Fläche.** Beide füllten mit `--XX-50`, das
1141
- im Light gleichzeitig der zarte Sektions-Tint ist. Als Bauteil-Füllung trug es gegen seinen Grund
1142
- nur **1,06:1** (ki) bis **1,14:1** (es), ein Fall auf einer `wo-50`-Sektion sogar 1,00:1: das
1143
- Element las nur noch als farbiger Text und verfehlte den Faustwert von 1,3:1 aus CONTRIBUTING § 5.
1144
- Gemessen über alle 64 Pills und 39 Bereichs-Badges der Doku, in beiden Modi.
1145
- Neu dafür **`--co-fill` / `--ki-fill` / `--es-fill` / `--wo-fill`** (Light `-200`, ki `-400`, weil
1146
- der Ton so hell ist, dass `-200` nur 1,27:1 trägt; im Dark identisch mit `--XX-50`, das dort schon
1147
- auf genau diese Aufgabe kalibriert ist). Nachgemessen: Light 1,42 bis 2,03:1 gegen beide Gründe,
1148
- Dark unverändert 1,32 bis 1,79:1, Text überall über 5,6:1. Die ES-Pill nimmt dabei `--es-800`
1149
- statt `--es-700`, wie das ES-Badge, weil `-700` auf der kräftigeren Füllung nur 4,74:1 trüge.
1150
- WCAG war in beiden Modi schon vorher erfüllt, der Text trug 4,7 bis 11,4:1; verfehlt war die
1151
- Flächenwirkung. Kein `--ro-fill`: Rosé ist Akzent- und Statusfarbe ohne Pill und Badge.
1152
- - **Bereichs-Rahmen der Team-Stimme erfüllt die 3:1-Schwelle.** `.team-voice[data-area]` tönt seinen
1153
- Rahmen in der Bereichsfarbe, und der Rahmen ist dort das tragende Farbsignal, weil das Quote-Icon
1154
- daneben dekorativ ist (`opacity` .25 im Light, .6 im Dark). Er trug im Light auf `-200` nur
1155
- **1,27:1** (ki) bis **2,03:1** (es) und im Dark auf `-800` nur **1,07:1** (wo) bis **1,63:1** (ki),
1156
- war also in beiden Modi als Signal nicht wahrnehmbar. Jetzt dieselben Stufen wie beim
1157
- Bereichs-Chip, die dort schon auf die 3:1-Schwelle für grafische Objekte ausgelegt sind: Light
1158
- `-600`/`-700`/`-500`/`-500` (3,29 bis 5,66:1), Dark `-300` (4,25 bis 9,48:1).
1159
- - **Sektions-Rhythmus im Dark auf Light-Parität, Karten grenzen sich per Rand ab.** Im Dark werden
1160
- `n-50`-Sektionen auf `bg-surface` gehoben, damit die Tonfolge dieselbe ist wie im Light. Für
1161
- kartentragende Sektionen gab es dafür eine Ausnahme (`.ep-section-cards`), die die Hebung
1162
- zurücknahm, damit die Karten die hellere Stufe bilden. Die Ausnahme kostet aber genau den
1163
- Rhythmus: die Sektion sitzt dann auf dem Ton ihrer Nachbarn. Im Browser über die 24 Beispielseiten
1164
- gemessen (133 Abschnitte, Chrome, `data-theme="dark"`): **63** verschmolzene Sektionsübergänge mit
1165
- Rücknahme, **20** ohne, **17** im Light. Auf der Beispiel-Startseite: 1 gegen 0.
1166
- Die Ausnahme entfällt daher komplett, samt Klasse und ihren 9 Vorkommen im Markup. Stattdessen
1167
- tragen Karten im Dark einen Rand aus `--bd-strong-c` (**4,21:1** gegen die Kartenfläche):
1168
- `.card`, `.ep-card` und `.ep-card-link`, letztere auch dann, wenn sie im Light `border:none`
1169
- setzen und sich auf `--e1` verlassen. Das ist kein Notbehelf, sondern dasselbe Mittel, mit dem im
1170
- Light die weiße Karte auf der weißen Sektion liest, dort mit 1,10:1. Auf `bg-page`-Sektionen
1171
- kommt die Tonstufe dazu. Gegenprobe im Browser: keine Kartenfläche ohne Rand.
1172
- - **Schatten nur noch auf Karten, deren Fläche selbst klickbar ist.** `.card-elevated` setzte den
1173
- Ruhe-Schatten `--e1` auf jeder Karte, auch auf `<article>`/`<div>`-Karten mit Buttons im Footer.
1174
- Nach der Konvention „Elevation = Interaktivität“ ist so eine Karte statisch: die Buttons sind die
1175
- Interaktion, die Fläche führt nirgendwohin, und ein Ruhe-Schatten verspricht Klickbarkeit, die es
1176
- nicht gibt. Der Hover-Lift war bereits auf `a.card-elevated` begrenzt, der Ruhe-Schatten nicht.
1177
- Die Variante hängt jetzt komplett an `a.card-elevated`; eine statische Karte fällt damit auf
1178
- `.card` zurück (flach, `--bd`-Rahmen) und kann den Schatten auch mit gesetzter Klasse nicht
1179
- bekommen. Betroffen waren 9 statische `.card-elevated`-Karten (generische Karten, Referenz- und
1180
- Themen-Karten der Beispielseiten), 6 Blöcke mit Inline-`box-shadow` (AI.Box-Preiskarten,
1181
- Agenda-Karten, zwei Newsletter-Widgets) sowie `.cta-dl` und `.cta-visual`, die den Schatten aus
1182
- dem CSS trugen. Alle behalten ihren Rahmen, die Kartenform bleibt. Die 44 Link-Karten
1183
- (`<a class="card card-elevated">`) sind unverändert. In der Doku ersetzt die Zeile „Link-Karte“
1184
- die bisherige Zeile „Elevation-Opt-in“, die genau diesen Fall legitimiert hatte.
1185
- - **Angular-Card rendert flach.** `CardComponent` gab per Default `card card-elevated` auf einem
1186
- `<article>` mit Footer-Button aus, also genau dieses Muster. Der `elevated`-Input entfällt (er
1187
- wäre nach der CSS-Änderung wirkungslos), die Komponente rendert `<article class="card">`. Eine
1188
- Link-Karten-Variante (`<a>` mit Ziel statt `<article>` mit Button) bleibt offen.
1189
- - **Bereichs-Chips waren im Dark dunkel-auf-dunkel.** `.chip[data-area]` setzte Rahmen und Text auf
1190
- die dunklen Stufen (`-500`/`-600`/`-700` Rahmen, `-700`/`-800` Text), die im Dark nicht flippen:
1191
- der Text stand zwischen **1,35:1** (es) und **2,36:1** (co) auf einer Karte, der es-Rahmen mit
1192
- 2,30:1 unter der 3:1-Schwelle für die Bedienelement-Grenze. Nur der *gedrückte* Zustand hatte
1193
- Dark-Regeln. Jetzt wie überall im Dark die hellen Pendants: Text `-200` (es `-100`) mit 7,66 bis
1194
- 10,25:1, Rahmen `-300` mit 4,25 bis 9,48:1, Hover eine Stufe kräftiger. Betrifft 35 Chips in der
1195
- Doku. `check:dark-states` sah das nicht, weil es Zustands-Regeln prüft, nicht Ruhezustände.
1196
- - **Karten-Varianten „Filled“ und „Outlined“ aus der Doku entfernt.** `.card-filled` und
1197
- `.card-outlined` existierten nie, weder im CSS noch im Markup noch in der Angular-Lib. Die
1198
- Varianten-Tabelle führt jetzt die zwei Zustände, die es gibt: statisch (`.card`) und Link-Karte
1199
- (`a.card.card-elevated`). Die Do/Don't-Liste und der Abschnitts-Untertitel sind nachgezogen,
1200
- ebenso zwei Aussagen zu einem „Tonal Overlay“, das Karten nie gesetzt haben.
1201
- - **Drei rohe Bereichston-Rahmen ersetzt.** Die Pro-Preiskarte trug `1px solid var(--ki-200)`
1202
- (im Light 1,27:1 gegen Weiß, im Dark eine leuchtende Haarlinie mit 10,25:1), jetzt `--bd`; die
1203
- 4-px-Oberkante und die „Empfohlen“-Pill tragen die Hervorhebung weiter. Die zwei
1204
- Font-Specimen-Links nutzen jetzt die vorhandene `.chip`-Komponente mit `data-area` statt
1205
- Inline-Rahmen, Inline-Radius und zwei `onmouseover`/`onmouseout`-Handlern für den Hover.
1206
- `.chip` bekommt dafür `text-decoration:none`, damit es auch als `<a>` trägt.
1207
- - **Dark-Mode-Flächen neu aufgesetzt.** `--bg-page` trug mit `#333E48` nur **10,92:1** gegen Weiß.
1208
- Die Material-Dark-Theme-Guidance fordert für die Basisfläche mindestens **15,8:1**; der Wert ist
1209
- kein Stilmittel, sondern der Puffer, der Fließtext auch auf der höchsten Elevationsstufe noch
1210
- 4,5:1 sichert. Ursache war strukturell: `#333E48` ist `--tx-brand` aus dem Light Mode, also eine
1211
- gegen Weiß optimierte **Textfarbe als Fläche**. Neu `#151A1F` (17,51:1), Farbwinkel 209 wie bisher,
1212
- der Marken-Slate bleibt erhalten. `--bg-surface` `#3F4B56` → `#28323D`; die Stufe zwischen Seite
1213
- und Karte wächst dabei von 1,223:1 auf **1,346:1**, weil mehr Kopfraum nach unten mehr Spielraum
1214
- nach oben schafft. Gemessen über alle 24 Beispielseiten im Dark: **117 → 24 Kontrast-Verstöße**,
1215
- die verbleibenden 24 sind ein vorbestehender, themeunabhängiger Befund am `.skip-link`
1216
- (weiß auf `--co-500` = 2,31:1, in Light identisch, separat zu beheben). Der größte Block waren
1217
- `--tx-muted` und Bereichstexte auf Karten, die mit 4,08:1 knapp unter AA lagen und jetzt bei
1218
- 5,95:1 liegen.
1219
- - **Getönte Flächen im Dark: `--XX-50` zerfällt in zwei Token.** Ein Wert kann zwei gegensätzliche
1220
- Aufgaben nicht mehr tragen. Ein **großes Band** muss dunkler bleiben als die Karten darauf, eine
1221
- **kleine Füllung** (Badge, Pill, Icon-Kachel) muss heller sein als ihr Grund, sonst liest sie
1222
- nicht mehr als Fläche. Bei `bg-page` auf 17,5:1 und Schwarz bei 21:1 reicht der Spielraum für
1223
- beides zusammen nicht. `--XX-50` ist jetzt die kleine Füllung (9,8:1, also 1,78:1 gegen die Seite
1224
- und 1,32:1 gegen Karten), das neue `--XX-band` die Sektionsfläche (16,5:1, 1,06:1 gegen die Seite
1225
- mit der Trennung über den Farbton, Karten darauf 1,27:1). Im Light sind beide identisch.
1226
- Gemessen über 199 Badge- und Pill-Instanzen: die Fläche gegen ihren Grund stand nach der ersten
1227
- Fassung bei **1,06:1** und damit praktisch nicht mehr da, jetzt bei 1,32 bis 1,79:1. Zum Vergleich
1228
- der Stand vor dem gesamten Umbau: 1,29:1 im schlechtesten Fall. Sättigung auf 0,60 begrenzt, weil
1229
- kräftig gesättigte Farbe großflächig auf dunklem Grund optisch vibriert.
1230
- - **Acht Inline-Rahmen nutzten rohes `--n-100` statt `--bd`.** Das verstößt gegen die eigene Regel in
1231
- `CONTRIBUTING.md` § 5. Im Light sind beide Werte identisch, im Dark wird `--n-100` dunkel und der
1232
- Rahmen stand mit **1,18:1** gegen die Kartenfläche, war also praktisch unsichtbar. Jetzt `var(--bd)`
1233
- (2,99:1). Betraf `.card`-Elemente in der Doku, Light bleibt unverändert.
1234
- - **Snackbar-Flächen im Dark waren hartkodierte Kopien.** `.snack-ok` und `.snack-err` trugen
1235
- `#052415` und `#220808`, also die alten Werte von `--c-success-bg` und `--c-error-bg`. Als die
1236
- Status-Tints angehoben wurden, blieben die Kopien zurück und standen nur noch **1,06:1** bzw.
1237
- **1,08:1** gegen die Seite, die Snackbar war als Fläche praktisch weg. Jetzt über die Token
1238
- (1,78 und 1,79:1). Zusätzlich zählt die Snackbar zu den schwebenden Panels und bekommt im Dark
1239
- denselben `--bd-strong-c`-Rand wie Menüs (5,66:1 gegen den Grund); das deckt auch die
1240
- Default-Variante ab, die mit `--n-700` nicht mitflippt und bei 1,23:1 lag.
1241
- - **Schwebende Panels bekommen im Dark einen stärkeren Rand.** Menüs und Popover grenzen sich im
1242
- Light über `--e3` ab. Auf der tiefen Basisfläche trägt ein schwarzer Schatten das nicht mehr: ein
1243
- geöffnetes Topnav-Menü stand über dem Hero-Foto nur noch **1,34:1** gegen die hellste Stelle
1244
- daneben (vorher 1,92:1), weil die Panel-Fläche mitgesunken ist, das Foto darunter aber nicht.
1245
- Eine dritte statische Flächen-Stufe wäre der Material-Weg, würde aber die Zwei-Stufen-Konvention
1246
- brechen und einem Menü über einer Karte nur 1,13:1 bringen. Stattdessen übernimmt der Rand:
1247
- `--bd-strong-c` statt `--bd-c` für `.ep-nav-sub`, `.ep-select-menu`, `.ep-combobox-menu` und das
1248
- Suchpanel. Panel-Rand gegen den Untergrund 3,51 auf **4,95:1**, damit über dem Ausgangsstand
1249
- (4,15:1). Neu dafür `--bd-c` / `--bd-strong-c`, die reinen Rahmenfarben: `--bd` und `--bd-strong`
1250
- sind Shorthands und funktionieren in `border-color` nicht.
1251
- - **Platzhalter in Formularfeldern erfüllen AA.** `.field` hatte keine `::placeholder`-Regel, es griff
1252
- Chromes Default `rgb(117,117,117)`: im Dark **2,82:1** und damit ein Verstoß, im Light 4,61:1 und
1253
- damit knapp bestanden, aber ohne Reserve und vom Browser abhängig. Jetzt `--tx-secondary` wie bei
1254
- Suchfeld, Footer-Newsletter, Combobox und Nav-Suche, die das längst taten; die Standard-Felder
1255
- waren der Ausreißer. Gemessen über 22 Felder: Dark 7,13:1, Light 6,29:1.
1256
- - **Zwei Download-Buttons trugen im Dark dunklen Text auf dunkler Fläche** (3,51:1). Sie setzten die
1257
- Bereichsfarbe inline (`--c500:var(--co-700)`) statt über `.btn-co`. Die Dark-Regel für die Fläche
1258
- hängt an der Bereichsklasse, die für die Textfarbe an `.btn-filled`; ohne Klasse greift nur die
1259
- zweite. Jetzt `class="btn btn-filled btn-co"`, die inline gesetzten `--c600`/`--c700`/`--c900`
1260
- waren ohnehin tot (nirgends in `css/` referenziert). Vorbestehend, Light war nicht betroffen.
1261
- - **Fehler-Badge-Text im Dark auf `#FFA5A5`.** `#F08080` trug auf der aufgehellten Fehler-Füllung
1262
- nur 3,78:1. Damit nutzen `--badge-err-text`, `--cbadge-fail-text` und `--c-error` denselben Ton,
1263
- eine Fehlerfarbe weniger im System.
1264
- - **`--c-warning-bg` war im Dark unsichtbar.** `#231800` stand mit 1,00:1 gegen die neue Seite. Alle
1265
- drei Status-Tints und die drei Kontrast-Badge-Flächen auf die Tint-Stufe gehoben.
1266
- - **Ränder im Dark nachgezogen.** Auf der tieferen Seite sprang `--bd` von 3,19:1 auf 5,12:1 und
1267
- wirkte drahtig, jetzt `#6F7A89`. `--field-border` von `--n-200` (11,46:1) auf `#869C9C`; als
1268
- einzige Grenze, die WCAG 1.4.11 zwingend braucht, liegt es mit 4,49:1 auf der Karte sicher über 3:1.
1269
- - **Logo-Platte im Dark gedämpft.** Eine reinweiße Fläche dieser Größe stand mit 17,51:1 gegen die
1270
- Seite. Über das neue `--bg-plate` im Dark auf `#E8EDED`; dunkle Kundenlogos darauf 14,73:1.
1271
- - **Fotos im Dark leicht gedämpft** (`filter: brightness(.92)` auf `.hero-image-media`,
1272
- `.ep-media-band`, `.article-figure`). Nicht zu verwechseln mit dem verworfenen
1273
- `brightness(.75)` auf Icon-Glyphen: das betraf Strichgrafik auf getönten Kacheln.
1274
- - **Fokusringe folgen jetzt `var(--bg-page)`** statt einem hartkodierten `#333E48` und laufen bei
1275
- künftigen Flächen-Änderungen automatisch mit.
1276
- - **Drei hartkodierte Kopien im Dark auf ihre Token gezogen.** Sie hielten Werte, die die
1277
- Flächen-Umstellung ersetzt hat, und liefen dadurch aus der Familie:
1278
- `.a11y-rule-icon` trug die Kreisflächen als `#052415` / `#220808` und stand damit bei 1,06:1 bzw.
1279
- 1,08:1 gegen die Seite, der Kreis war keine Fläche mehr (jetzt `--c-success-bg` / `--c-error-bg`,
1280
- 1,78 und 1,79:1; der Fehler-Glyph geht mit auf `--c-error`, 5,21:1 statt 3,78:1).
1281
- Der Chip-Rahmen hielt den alten `--bd`-Wert `#818C99` (jetzt `--bd-strong-c`, 4,21:1 gegen die
1282
- Chip-Fläche; `--bd-c` wäre mit 2,99:1 unter der 3:1-Schwelle, die die Bedienelement-Grenze nach
1283
- WCAG 1.4.11 braucht). Der Tonal-Button-Rahmen hielt den alten `--bd-strong`-Wert `#9DA8B6`
1284
- (jetzt `--bd-strong-c`, 3,64:1 bis 4,87:1 auf den vier Tonal-Füllungen).
1285
-
1286
- - **Skip-Link erfüllt AA.** Weiß auf `--co-500` `#00BEBE` trug **2,31:1**. In Ruhe ist der Link
1287
- geclippt, beim Fokus springt er sichtbar herein, also genau dann kaputt, wenn Tastaturnutzende ihn
1288
- brauchen. Jetzt `--co-700` (5,52:1), dieselbe Füllfarbe, die `.btn-filled.btn-co` verwendet.
1289
- Betraf beide Modi, `--co-*` flippt nicht.
1290
- - **Hero-Scrim verdichtet.** Der Text liegt zwischen 23 % und 77 % der Caption-Höhe, der Eyebrow als
1291
- erstes Kind damit im obersten, schwächsten Abschnitt des Verlaufs. Dort stand er auf Alpha 0,30,
1292
- über einem ausgebrannt hellen Foto sind das 1,90:1; gemessen lagen bis zu **63 % der
1293
- Eyebrow-Fläche** unter 4,5:1. Der `text-shadow` hilft optisch, zählt für WCAG nicht. Die
1294
- Unterkante bleibt unverändert bei `.85`, verdichtet wird nur der obere Teil
1295
- (`.72` bei 60 %, `.66` bei 80 %). Bei 77 % jetzt Alpha 0,67 = 6,0:1 gegen ein weißes Foto.
1296
- Pixelgemessen über 51 Textblöcke auf 17 Seiten: Blöcke mit über 10 % Fläche unter 4,5:1 gehen von
1297
- **17 auf 0** (Light) und von 13 auf 0 (Dark).
1298
- Der erste Anlauf dafür erfüllte zwar alle Kontrastwerte, zeichnete aber eine sichtbare Linie quer
1299
- durchs Bild: er baute die Deckkraft auf zu kurzer Strecke auf. Gemessen an der zweiten Ableitung
1300
- der Zeilenhelligkeit lag er bei 21 bis 25 gegenüber 7,9 beim vorherigen Verlauf. **Mehr
1301
- Stützstellen halfen nicht** (eine weiche Kurve mit neun Stops maß 22,4). Gelöst über eine längere
1302
- Auslaufstrecke: die Caption bekommt oben eine fluide Polsterung bis 96 px, der Verlauf läuft mit
1303
- 0,0066 Deckkraft pro Pixel aus und misst 9,2. Der Faustwert dazu steht jetzt in der Bildsprache.
1304
- - **Code-Kommentare erfüllen AA.** `.cb-body .c` stand mit `--n-400` auf dem `--n-50`-Grund bei
1305
- 3,65:1, jetzt `--tx-muted` (Light 4,51:1, Dark 8,9:1). Die Terminal-Variante hat einen fest
1306
- dunklen Grund in beiden Modi und behält deshalb `--n-400` (4,87:1) über eine eigene Regel.
1307
- - **Scrim-Empfehlung in der Bildsprache korrigiert.** Die Doku nannte `rgba(0,0,0,.45)` und
1308
- widersprach damit ihrer eigenen Regel darüber: über einer ausgebrannten Bildstelle trägt das nur
1309
- rund 3:1. Neu mindestens `.60` für weiße Schrift, gerechnet gegen die **hellste** Stelle unter dem
1310
- Text statt gegen den Bilddurchschnitt, und bei Verläufen an der Position des Textes statt am
1311
- dichten Ende. Genau diese Lücke hatte den Hero-Befund erzeugt.
1312
-
1313
- ### Documentation
1314
- - **Die Rollen-Token sind jetzt dort dokumentiert, wo man sie sucht.** `--co-ink`, `--es-ink` und
1315
- `--wo-ink` standen in keiner Doku, die Token-Tabelle der Doku-Seite führte nur `--ki-ink` (die
1316
- anderen drei gab es bis vor Kurzem nicht), und `--XX-fill` stand nur in Prosa und in
1317
- CSS-Kommentaren. Auch die Consumer-Tabelle in `docs/GETTING-STARTED.md`, also die Tabelle, die
1318
- eine Anwendung liest, kannte beide Rollen nicht. Ergänzt: Familien-Zeilen in der Doku-Tabelle mit
1319
- Messwerten, dieselben zwei Rollen in der Consumer-Tabelle samt Leitsatz („nach der Rolle greifen,
1320
- nicht nach der Stufe“), die Rollen-Zuordnung als ersten Schritt in CONTRIBUTING § 10, beide
1321
- Prüfbefehle im Schritt „Prüfen“ und in der PR-Checkliste, und ein Abschnitt „Prüfungen“ im README:
1322
- dass es ein Kontrast-Gate gibt und was es garantiert, stand dort gar nicht.
1323
- - **In die PR-Checkliste aufgenommen, was in dieser Runde zweimal gefehlt hat:** nach
1324
- Layout-Änderungen den geänderten Bereich in beiden Modi **und zwei Breiten** ansehen, also Kanten
1325
- und Umbrüche, nicht nur Farbwerte.
1326
-
1327
- ### Added
1328
- - **Angular-Komponenten-Bibliothek `@conciso/design-system-angular`.** Die 37 Angular-Wrapper
1329
- sind aus dem Storybook in eine eigene, publizierbare Lib extrahiert (Angular Package Format
1330
- via ng-packagr, ein einziger Einstiegspunkt `public-api.ts`, Angular 21). Die Wrapper sind
1331
- dünne Hüllen über der CSS-Schicht und liefern **kein eigenes CSS**: Konsumenten binden
1332
- `@conciso/design-system` (peerDependency, Lockstep-Version) samt Fonts global ein — der
1333
- copy-paste-fertige `angular.json`-Schnipsel steht im README der Lib. `storybook-angular`
1334
- enthält nur noch Stories und konsumiert die Lib. Architektur-Entscheidungen in
1335
- `docs/adr/0001`–`0005`, `CONTEXT.md` führt das Glossar.
1336
- - **Distribution über GitHub Packages** (privat, org-scoped) für beide Pakete — kehrt die
1337
- 0.1.0-Entscheidung „intern/proprietär statt Registry-Publish“ um, weil eine Angular-Lib als
1338
- gebautes Artefakt ausgeliefert werden muss und ein Git-Tarball dafür nicht genügt.
1339
- `publishConfig` auf beiden `package.json`, dazu ein Publish-Workflow, der auf eine
1340
- Versionsänderung auf `main` reagiert: Er prüft pro Paket, ob die Lockstep-Version schon in der
1341
- Registry liegt, veröffentlicht nur was fehlt, und legt Tag plus GitHub-Release selbst an
1342
- (Release-Text ist der CHANGELOG-Abschnitt der Version). Version anheben und mergen ist damit
1343
- das Release.
1344
- - **Consumer-Smoke-Test als CI-Gate.** Eine committete Minimal-Konsumenten-App
1345
- (`examples/consumer-fixture`) installiert die per `npm pack` gebauten Tarballs beider Pakete
1346
- und fährt einen produktiven AOT-Build — außerhalb des Repos, damit die Modul-Auflösung nicht
1347
- über das Wurzel-`node_modules` leckt. Fängt, was Lint und Storybook strukturell nicht sehen:
1348
- unvollständige APF-Metadaten, fehlende Re-Exports, nicht deklarierte Abhängigkeiten,
1349
- AOT-Template-Typfehler. Ist harte Vorbedingung des Publish-Jobs.
1350
- - **Störer (`.stoerer`), Verweiskacheln über dem Hero, nur auf der Startseite.** Ein bis drei
1351
- Kacheln als Set oben rechts über dem Hero-Bild, je auf einen aktuellen Inhalt: nächste
1352
- Veranstaltung, neuer Wissensbeitrag, Pressemitteilung, Info. Default sind zwei, drei sind das
1353
- Maximum. Aufbau je Kachel: Typ-Glyph und Thema-Label in einer Zeile (Eyebrow, `--co-ink`), Titel
1354
- über zwei Zeilen, Meta-Zeile. Die ganze Kachel ist der Link, ein Tab-Stop, mit Hover und
1355
- Fokus. Klassen `.stoerer-hero` (Wrapper um Hero und Set) · `.stoerer-set` · `.stoerer-list` ·
1356
- `.stoerer` · `.stoerer-topic` · `.stoerer-title` · `.stoerer-meta` ·
1357
- `.stoerer-icon`. `.hero-image` bleibt unverändert und funktioniert weiter ohne Störer.
1358
- Die Entscheidungen, die das Bauteil tragen:
1359
- - **Typ-Glyph als Leading-Element im Thema-Label**, nicht rechts hinter einem Trenner. Material
1360
- trennt die Slots einer Listenzeile nach Aufgabe: das Leading-Element „represents the item's
1361
- subject or category“, das Trailing-Element trägt „secondary information, actions, or status
1362
- indicators“. Das Glyph ist ein reiner Typ-Marker, also eine Kategorie. Rechts stand es im Slot
1363
- für Aktionen und Status und konnte auf einer vollständig klickbaren Kachel als Button lesen.
1364
- NN/g ergänzt die andere Richtung („a text label must be present alongside an icon to clarify
1365
- its meaning“): die Bedeutung trägt das Label, das Glyph ist Wiedererkennungshilfe und wirkt
1366
- direkt neben dem Wort, das es doppelt. Gemessener Nebengewinn: ohne Icon-Spalte (57 px aus
1367
- Glyph, Polsterung, Trenner und Spaltenabstand) wächst die Textspalte von 269 auf 326 px, also
1368
- um 21 %, und die realistischen Titel der Startseite klammern nicht mehr. Kachelhöhe unverändert
1369
- 126 px, weil das 16-px-Glyph in die 16 px hohe Label-Zeile passt. Preis ist die Salienz: 16 px
1370
- mit `--icon-stroke-micro` statt 24 px mit `-sm`, vertretbar weil das Glyph `aria-hidden` ist.
1371
- Damit entfällt `.stoerer-body`, dessen einziger Zweck der durchgehende Trenner war.
1372
- - **Eine Farbgebung für alle vier Typen**, der Inhaltstyp steht in Label und Icon. Vier
1373
- Bereichsfarben nebeneinander arbeiten gegeneinander und gegen die Hero-Headline, und das Set
1374
- liest dann als vier lose Kacheln statt als eines. Akzent ist durchgehend Corporate
1375
- (`--co-ink`), aus demselben Grund wie bei der Topnav: der Störer ist Chrome über dem Bild, kein
1376
- Bereichsinhalt. Kein `data-area`.
1377
- - **Deckende Fläche, kein Glas.** Über einem Foto ist der Grund unbekannt, eine halbtransparente
1378
- Kachel trägt je nach Bildstelle 2:1 oder 12:1, und `check:contrast` kann das nicht werten, weil
1379
- der Grund nicht in der Elternkette steht. Auf `--bg-surface` ist der Text messbar: Light 5,52
1380
- bis 10,92:1, Dark 8,32 bis 10,48:1. Das Bild bleibt sichtbar, weil die Kacheln klein sind, nicht
1381
- weil sie durchscheinen. Dazu ein `--bd-strong`-Rahmen schon im Light, abweichend von den
1382
- übrigen Karten: auf einer ausgebrannt hellen Bildstelle leistet der schwarze `--e1`-Schatten
1383
- keine Kante mehr.
1384
- - **Gleiche Höhe unabhängig von der Titellänge.** `min-height:2lh` außen reserviert zwei Zeilen,
1385
- das innere Element kappt per `line-clamp` mit Auslassungszeichen. Ein einzeiliger Titel lässt
1386
- dadurch sichtbar Luft zur Meta-Zeile, und das ist der gewollte Tausch: sonst springen die
1387
- Kacheln, sobald das CMS einen längeren Titel liefert. Der vollständige Titel bleibt im
1388
- zugänglichen Namen des Links, das Kappen ist rein visuell.
1389
- - **Overlay nach Hero-Breite, nicht nach Fensterbreite.** Bei 21:9 ist die Hero-Höhe die Breite ×
1390
- 9/21; drei Kacheln brauchen 426 px Höhe und damit 994 px Hero-Breite. Eine Media Query auf
1391
- 1025 px ließ ein Dreier-Set in der Doku-Vorschau (Hero 898 px im 1200-px-Fenster) 41 px in die
1392
- Sub-Sektion ragen. Die Entscheidung hängt deshalb an einer **Container-Query** auf
1393
- `.stoerer-hero` (Schwelle 1025 px, das Desktop-Tier der Responsive-Strategie), der ersten im
1394
- System. Ist der Hero schmaler, steht das Set als Block darunter; dasselbe passiert ohne
1395
- Container-Query-Support, dort greift die Regel nie und der funktionierende Fall bleibt stehen.
1396
- Barrierefreiheit: `<aside aria-label="Aktuelles">` als benannte Landmark, `<ul>`/`<li>` damit
1397
- Screenreader die Anzahl melden, Icon `aria-hidden` (es wiederholt das Label). Der Trennpunkt der
1398
- Meta-Zeile ist `aria-hidden` mit einem `.sr-only`-Komma daneben, sonst wird er verschluckt oder
1399
- als „Punkt“ gelesen und Datum und Ort verschmelzen; im AX-Tree geprüft. Der Hover unterstreicht
1400
- zusätzlich den Titel (WCAG 1.4.1): über einem Foto ist ein Schatten- oder Flächen-Zuwachs je nach
1401
- Bildstelle kaum sichtbar, die Unterstreichung immer.
1402
- Ziel jeder Kachel ist die **eigene Seite des Inhalts** (Veranstaltungs-Detailseite,
1403
- Wissensbeitrag, Pressemeldung, Seminar-Landingpage), nie ein Anker der Startseite und kein
1404
- `target="_blank"`. Ein Anker wäre ein Versprechen, das die Kachel nicht hält: ihr zugänglicher
1405
- Name kündigt einen konkreten Inhalt an, geliefert würde eine Scroll-Position (WCAG 2.4.4).
1406
- Existiert für einen Anlass keine eigene Seite, ist er kein Störer-Kandidat.
1407
- Rolle gegenüber den Vorschau-Sektionen: der Störer ist eine **Abkürzung zum Inhalt**, dasselbe
1408
- Ziel wie eine Sektion weiter unten ist erlaubt und beabsichtigt. Die Sektion bleibt der kanonische Ort mit Bild
1409
- und Anreißer. Bedingung ist ein **Domänenwort pro Domäne**: sonst steht derselbe Inhalt zweimal
1410
- als Link mit verschiedenen Namen auf der Seite, und für Screenreader sind das zwei verschiedene
1411
- Dinge. Der Titel muss dafür im zugänglichen Namen der Kachel stehen, was der Aufbau von selbst
1412
- leistet.
1413
- - **Icon `ui-megaphone`** (outline, `--icon-stroke-sm`) für den Störer-Typ Pressemitteilung.
1414
- `ui-newspaper` heißt im DS „Zeitung / Artikel“ und bleibt das Artikel-Glyph für den
1415
- Wissensbeitrag; eine Verlautbarung braucht ein eigenes Zeichen, sonst ist das Icon zwischen den
1416
- beiden Typen kein Unterscheidungsmerkmal mehr. Veranstaltung nutzt `ui-calendar-days`, Info
1417
- `ui-information-circle`, beide bereits vorhanden.
1418
- - **`--bg-surface-hover`** (Light `var(--bg-surface)`, Dark `#2E3B46`): Auf der tieferen Basisfläche
1419
- tragen die schwarzen `--e*`-Schatten weniger, deshalb hebt der Hover interaktiver Karten
1420
- zusätzlich die Fläche. Ein Zustand, keine dritte statische Flächen-Stufe.
1421
- - **`--bg-plate`** (Light `#FFFFFF`, Dark `#E8EDED`): helle Platte unter Fremd-Assets, die nur in
1422
- einer dunklen Fassung vorliegen (Kundenlogos).
1423
- - **Druckausgabe:** `dark-mode.css` steht jetzt komplett in `@media screen`, Token-Block und
1424
- Komponenten-Regeln. Eine Seite mit `data-theme="dark"` druckte bisher die volle dunkle Fläche;
1425
- jetzt greifen die Light-Werte aus `tokens.css`, ohne dass sie ein zweites Mal gepflegt werden.
1426
- Die Komponenten-Regeln müssen mit hinein: 183 von ihnen setzen eine helle Tint- oder Festfarbe
1427
- (`co-200`, `ki-200`, `es-100`, heller `tx-primary`), die auf dem hellen Druckgrund 1,2 bis 1,9:1
1428
- trägt; ein geschützter Token-Block allein hätte den Text genau dort verloren, wo eine eigene
1429
- Dark-Regel greift. Neue Dark-Regeln gehören innerhalb des Blocks, sonst drucken sie dunkel mit.
1430
- - **CI-Gate gegen dunkel-auf-dunkel-Zustände** (`npm run check:dark-states`, in `css-core.yml`):
1431
- Prüft jede `:hover`/`:focus`/`:active`-Regel in `components.css` darauf, ob sie einen nicht
1432
- theme-awaren Ton setzt, dessen Kontrast gegen die dunklen Grundflächen unter der WCAG-Schwelle
1433
- liegt (4,5:1 für Text, 3:1 für Ränder), ohne dass `dark-mode.css` nachzieht. Gerechnet wird echter
1434
- Kontrast, nicht die Stufennummer: `--co-500` ist `#00BEBE` und trägt im Dark, `--wo-800` ist
1435
- `#183A0E` und nicht. Auf dem Stand vor diesem Commit hätte der Check **acht** Verstöße gemeldet.
1436
- Bewusste Ausnahmen stehen mit Begründung im Skript, aktuell keine.
1437
- Dazu ein zweiter Check gegen **zerrissene Füllung/Text-Paare**: Eine Regel, die Hintergrund und
1438
- Textfarbe gemeinsam setzt, trägt ihren Kontrast selbst, aber nur solange das Paar zusammenbleibt.
1439
- Überschreibt eine andere Regel nur eine Hälfte, entsteht genau der Fehler, den der erste Check nicht
1440
- sieht. Der zweite bildet dafür die Kaskade je (Element, Theme, Zustand) nach und prüft die
1441
- tatsächlich gewinnende Kombination — paarweises Vergleichen reichte nicht, weil im Dark oft eine
1442
- spezifischere Regel den Text längst überschrieben hat.
1443
- Er hat sofort zwei Fehler gefunden, die von Hand durchgerutscht waren: die zu breite Chip-Inversion
1444
- und einen Icon-Hover auf `var(--n-100)`. Letzterer entlarvte eine falsche Annahme im ersten Check:
1445
- Tokens aus dem Dark-Block galten als „theme-aware und damit unkritisch“, aber die Neutrals kippen
1446
- dort auf **dunkle** Werte (`--n-100` = `#1C2E2E`). Der Check wertet Tokens jetzt mit dem Wert aus,
1447
- den sie im Dark tatsächlich annehmen.
1448
- - **Personengruppe mit Bio** (`.author-card-group.is-grid`): Zweispaltige Variante der bestehenden
1449
- Author-Card-Gruppe für zwei bis vier Personen mit Kurz-Bio, gedacht für die Trainer:innen einer
1450
- Seminar- oder Training-Landing. Die Karten selbst bleiben unverändert; die Variante setzt nur das
1451
- Raster (960 px Container, `minmax(0,1fr)` gegen Grid-Blowout, mobil einspaltig ab 768 px) und zieht
1452
- die gemeinsame `.author-card-group-eyebrow` per `:has()` auf dieselbe Breite. Dokumentiert mit einer
1453
- Regel an der Personenzahl: 1 Person groß mit 4:3-Bild, 2 bis 4 im Raster, darüber das
1454
- Team-Tile-Grid ohne Bio. Dokumentiert unter *Seminar & Training · Trainer:innen*, nicht bei der
1455
- Author Card: Trainer:innen kommen nur auf Seminar- und Training-Landings vor. Live auf der Beispielseite *Scrum Trainings*, deren Trainer:innen-Sektion
1456
- bisher nur einen Platzhaltertext ohne Personen trug.
1457
- - **Komponente „Buchungsformular“** (`#sec-booking`, Klassen `.bk-*`): verbindliche Terminbuchung als
1458
- Komposition aus `.field`-Feldern, am Beispiel eines Seminars. Erste Formular-Komponente mit
1459
- **bedingten Feldblöcken** (Firma vs. Privatperson, abweichende Rechnungsadresse: `hidden` statt
1460
- `disabled`, `required` wird über `data-required` mitgeschaltet) und einem **Teilnehmenden-Repeater**
1461
- (Anzahl als führendes Feld, Namensblöcke folgen, Schutz vor stillem Datenverlust beim Verringern,
1462
- „Ich nehme selbst teil“ belegt Block 1 vor). Live-Preiszeile und Anzahl als `role="status"`, Fehlerübersicht `.bk-errors` mit Sprunglisten,
1463
- `autocomplete`-Sections je Block, feldspezifische Fehlermeldungen über `data-err`.
1464
- Informationshierarchie in drei Stufen: Gruppe `--ty-title-sm` (20 px) mit Haarlinie, Untergruppe
1465
- (`.bk-subgroup`/`.bk-sublegend`) `--ty-name` (14 px), Feldlabel 12 px Versalien. Eine Gruppe ist
1466
- eine Entscheidung samt ihrer Folgen, bedingte Blöcke liegen als Untergruppe **in** der Gruppe
1467
- ihres Auslösers statt daneben. Preiszeile zweimal (bei der Anzahl mit `role="status"`, stumm über
1468
- dem Submit); nur Pflichtsternchen ohne zusätzliche „(optional)“-Marker; freiwilliges
1469
- Contentletter-Häkchen von den Pflicht-Bestätigungen abgesetzt.
1470
- **Bestellübersicht** (`.bk-order`) unmittelbar vor dem zahlungspflichtigen Button: Leistung,
1471
- Termin, Auftraggeber, Plätze und Gesamtbetrag, live aus dem Formular. Noch leere Zeilen bleiben
1472
- stehen und tragen `data-empty`, damit sichtbar ist, was fehlt, statt dass die Übersicht springt.
1473
- Dazu der Abschnitt **„Für die Umsetzung“**: Anforderungen an die Produktivfassung, allen voran
1474
- ein Zwischenspeicher gegen Datenverlust (Schlüssel, Speicherort, Wiederherstellungsreihenfolge,
1475
- `beforeunload`-Regel), sowie serverseitige Validierung, Platzkontingent, Doppel-Submit und Spam.
1476
- Dazu ein Entscheidungs-Abschnitt „Anfrage oder Direktbuchung“, der das Formular gegen die adaptive
1477
- Kontaktseite abgrenzt; die Beispielseite Seminar bleibt bewusst beim Anfrage-Flow.
1478
-
1479
- - **Bereichs-Varianten für das Segmented Control** (`.seg-ki`, `.seg-es`, `.seg-wo`): Die Füllung des
1480
- gewählten Segments läuft jetzt über `--seg-fill`/`--seg-on` statt fest über `--co-700`, analog zum
1481
- `--c500`-Muster der Buttons. Default bleibt Corporate, bestehende Verwendungen ändern sich nicht.
1482
- Im Dark Mode kippt jede Variante auf ihren `-300`-Ton mit `-900`-Text.
1483
-
1484
- - **Doku · Abschnitt „Responsive“** (`#sec-responsive`): Übergabe-Spezifikation der Responsive-Strategie
1485
- als eigener Foundations-Abschnitt (bisher nur implizit im CSS + verstreut). Verbindliche 3-Stufen-Breakpoints
1486
- (Phone ≤ 520 · Mobile ≤ 768 · Tablet 769 bis 1024 · Desktop > 1024), Begründungen der nicht offensichtlichen
1487
- Bruchpunkte (520 vs. 768 bei Hero-CTAs, auto-fit vs. fix-spaltig, Hamburger ≤ 760, Doku-Sidebar ≤ 1024),
1488
- fluide Typo-Tokens (clamp, min ≥ 20 px, unitless Ratio) und vollständiges Komponenten-Inventar mit CSS-Quelle.
1489
- Plus Hinweise, was auf der echten Site übernommen werden muss vs. was mockup-gebunden ist.
1490
- - **Angebots-Detailseiten (Beispielseiten)**: Fünf neue Landingpages für einzelne Angebote unterhalb der
1491
- Bereiche, jeweils an den Bereich gebunden. Wirksame Organisationen: „Erste Hilfe bei Meetingflut“
1492
- (Festpreis 3.600 €), „Scrum Trainings“ (Preiskarten Scrum.org/TÜV SÜD), „Lean Portfolio Management“
1493
- (Beratung ohne Festpreis). Effektive Software: „Identity mit Keycloak“ (mit YouTube-Embeds) und
1494
- „Keycloak-Erweiterungen“ (Festpreis 9.900 €). Die Bereichs-Tabs (ES, WO) sind dafür zu aufklappbaren
1495
- Baum-Tabs geworden; die „Konkrete Themen“-Einträge der Bereichsübersichten verlinken jetzt auf die Seiten.
1496
- - **Video-Embed (Beispielseite Identity mit Keycloak)**: Zwei responsive YouTube-Embeds
1497
- (`youtube-nocookie.com`, 16:9 via `aspect-ratio`, `title`, kein Autoplay, `loading="lazy"`). Referenzen
1498
- (Social Proof) direkt nach der Lösung plus Mid-Page-CTA; Abschnitts-Hintergründe neu alterniert.
1499
- - **Doku · Page-Pattern „Angebots-Detailseite“** (`#sec-leistung-detail`): Aufbau (Sektionsreihenfolge nach
1500
- Funnel-Logik), Angebots-Box (Festpreis-Box + „Für wen“), Preismodelle (Festpreis / mehrere Pakete /
1501
- Beratung ohne Festpreis), Cross-Links und Verwendung (Do/Don't).
1502
- - **Doku · Barrierefreiheit „Video-Embed“** (`#gt-a11y-video`): `title`, Untertitel (WCAG 1.2.2),
1503
- Transkript/Audiodeskription, kein Autoplay, responsiv, Datenschutz, Sprach-Kennzeichnung, Tastatur;
1504
- inklusive Abgrenzung, was das DS liefert vs. was die Videoquelle liefern muss.
1505
- - **KI-Wissensbeitrag (Beispielseite): Contentletter- + LinkedIn-CTA**: Nach „Weiterlesen“ und vor dem
1506
- finalen CTA-Band ein „Dranbleiben“-Block als offene Feature-Liste (`.ep-feature`, zwei `col-6`): „Food
1507
- for your brain!“ (Contentletter-Anmeldung) und „Stay connected!“ (LinkedIn-Folgen, externer Link mit
1508
- `target="_blank"`/`aria-label`). Bewusst als flache Feature-Liste statt Karte/Band, um sich vom Download-
1509
- `.cta-dl` darüber abzuheben, ohne die Seite weiter zu verkasten. Corporate-Akzent (unternehmensweit).
1510
- - **`.card-cta-link` als echter Link nutzbar**: `a.card-cta-link` ohne Default-Underline (Underline erst
1511
- bei Hover), sichtbarer Fokus-Ring; gescoped auf `a[…]`, damit die `<span>`-Nutzung in klickbaren
1512
- `.ep-card-link`-Karten unberührt bleibt.
1513
- - **Farb-Swatches: Hex-Anzeige + Klick-zum-Kopieren** (Doku): In der Tonal-Palette (`#sec-colors`)
1514
- zeigt jeder Swatch zusätzlich zum Stufen-Label seinen Hex-Code (kontrastgleich zur Stufen-Schrift,
1515
- für die Nutzung in anderen Gestaltungsmitteln). Jeder Swatch und jede Farb-Clip-Card kopiert per
1516
- Klick oder Tastatur (Enter/Space) den Hex in die Zwischenablage (`navigator.clipboard`, Fallback
1517
- `execCommand`). Barrierefrei: `role="button"`, `tabindex`, `aria-label`, sichtbarer Inset-Fokus-Ring
1518
- (nicht vom `overflow:hidden` der `.pal-row` abgeschnitten), `aria-live`-Bestätigung + „✓“-Overlay.
1519
- In den schmalen Bereichs-Übersichtskarten (`#sec-areas`) bleibt der Hex aus Platzgründen im
1520
- title-Tooltip, Kopieren funktioniert dort ebenso.
1521
- - **Icon-Bibliothek** als maschinenlesbarer Export: `icons/icons.json` · `icons.js`
1522
- (kompletter `<svg>`-Body pro Key, `currentColor`, Stil-Markierung `solid`/`outline`,
1523
- `viewBox`, Verwendungskontext) + kuratierte Quelle `icons/source/*.svg`, generiert via
1524
- `scripts/build-icons.mjs` (`npm run build:icons`, jetzt Teil von `npm run build`).
1525
- Neue Exports `@conciso/design-system/icons` · `/icons.json`; Mapping/Consumption in
1526
- `icons/README.md`. Vier Solid-Bereichs-Glyphen (`ki-bot`, `es-window-check`,
1527
- `wo-network`, `co-building`) + generische Outline-UI-Icons.
1528
- - **Dropdown-Komponenten**: `.ep-select` (Custom Select mit Listbox-A11y, Bereichs-Akzent)
1529
- und `.ep-combobox` (Tipp-Filter über langen Listen; `.is-multi` für Multi-Select mit
1530
- entfernbaren Chips), inkl. Lösch-Button und Filter-Reset beim Schließen.
1531
- - **Topnav-Aktionen**: Such-Popover (Disclosure, A11y) + Light/Dark-Umschalter
1532
- (`aria-pressed`, synchron mit dem Sidebar-Switch), per JS in jede `.ep-topnav` injiziert.
1533
- - **Responsive Grid-Marker** für gezielt mehrspaltige Mobile/Tablet-Layouts (statt der
1534
- einspaltigen Default-Kollabierung): `data-team-tiles` (2×2), `data-team-roster` (2 Spalten
1535
- ≤ 520 px / 3 Spalten 521–768 px), `data-benefits` und `data-event-meta` (2×2 auf Tablet /
1536
- 1-spaltig ≤ 520 px). Alle auf `.layout-grid` gesetzt.
1537
-
1538
- - **Editoriale „Im Detail“-Sektion** auf der Beispielseite Wirksame Organisationen: offene
1539
- Feature-Liste (`.ep-feature`, Alternative zu Karten) mit bis zu 8 Themen-Landingpages, Status
1540
- über die Aktionszeile (Link „Zur Landingpage“ bei verfügbaren, „Landingpage folgt“ bei in-Aufbau,
1541
- ohne toten Link), plus content-breites Akzentbild als Sektions-Auftakt.
1542
- - **Bereichsvariante `.card-cta-link[data-area="co|ki|es|wo"]`**: färbt den Text-CTA-Link in der
1543
- Akzentfarbe statt Corporate-Teal (Shades wie `.ep-card-cta`), inkl. Dark-Overrides (`-200/-100`).
1544
-
1545
- ### Changed
1546
- - **Preis und Rahmendaten stehen jetzt direkt nach dem Hero** (Beispielseite Seminar): Die
1547
- Lernziele-Sektion ist zweispaltig geworden, links die Bullet-Liste (`.col-8`), rechts der
1548
- Angebots-Kasten (`.col-4`, sticky). Gemessen rückt der Preis von **45 % auf 10 %** der Seitenhöhe.
1549
- Bewusst kein neuer Streifen unter dem Header, sondern der vorhandene Kasten neben dem ersten Inhalt.
1550
- Das Kontaktformular bleibt, wo es war, bei „Inhalte & Voraussetzungen“; beide Kästen tragen dadurch
1551
- je eine Aufgabe. Dazu eine Zwei-Spalten-Variante der Rahmendaten (`.ep-facts.is-grid`): Einspaltig
1552
- war der Kasten 470 px hoch gegen 256 px Inhalt daneben, also 214 px Leerraum. Zweispaltig sind es
1553
- 299 px und 43 px Leerraum. Die Variante verzichtet auf die Haarlinien und wird über `auto-fit` in
1554
- schmalen Kästen von selbst wieder einspaltig.
1555
- - **Fakten-Streifen aufgelöst, Angaben ziehen in den vorhandenen Kasten** (`.ep-facts`): Der flache
1556
- Key-Facts-Streifen unter dem Hero entfällt auf allen vier Angebots-/Seminar-Landings. Die Angaben
1557
- bleiben vollständig erhalten und stehen jetzt dort, wo über das Angebot entschieden wird, statt an
1558
- zwei Stellen. Neu ist dafür `.ep-facts`, eine einspaltige `<dl>` aus Label/Wert-Paaren
1559
- mit Haarlinie dazwischen, **ohne eigenen Rahmen**: sie zieht in einen Container ein, den die Seite
1560
- schon hat (Angebots-Box, Sticky-Sidebar), sonst entstünde Kasten im Kasten. Damit verschwinden auch
1561
- die viermal kopierten Inline-Styles des alten Streifens.
1562
- Pro Seite: *Seminar* → Dauer, Format, Gruppe und Sprache in die Sticky-Sidebar (der dortige separate
1563
- Sprache-Block entfällt, die Dublette zum Streifen ist damit weg). *Meetingflut* und
1564
- *Keycloak-Erweiterungen* → **keine** Liste, ihre Angebots-Box nannte Laufzeit, Leistung, Ergebnis und
1565
- Preis bereits vollständig, der Streifen war dort reine Dopplung. *Scrum Trainings* → eigener Kasten
1566
- einmal über den vier Preiskarten, weil die Angaben für alle vier gelten und die Seite keine
1567
- Angebots-Box hat.
1568
- - **`data-accent` ist nicht mehr an `.ep-page` gebunden**: Die Akzent-Regeln für `.t-co` und
1569
- `.body-link` sowie die Chrome-Resets für Footer und Topnav laufen jetzt über `[data-accent="…"]`
1570
- statt `.ep-page[data-accent="…"]` (Light und Dark). Damit kann auch ein einzelner Block einen
1571
- Bereichs-Scope aufspannen, etwa ein Buchungsformular, eine Anmeldesektion oder ein
1572
- Bereichsformular in einer sonst corporate Seite, ohne Inline-Bereichsfarben an den Links (die im
1573
- Dark-Mode brechen würden). Abwärtskompatibel: die bestehenden `.ep-page[data-accent]`-Seiten
1574
- treffen den neuen Selektor unverändert.
1575
- - **Topnav: Submenüs öffnen zusätzlich per Hover**: Auf Geräten mit echtem Hover (`pointer:fine`) klappt
1576
- das Submenü jetzt auch beim Überfahren des Top-Items auf (JS-gesteuert, kurzer Intent-Delay beim Öffnen,
1577
- verzögertes Schließen + unsichtbare Brücke über den Gap → WCAG 1.4.13 „hoverable/dismissible/persistent“).
1578
- Klick/Tap, Tastatur und Touch bleiben unverändert; der Label-Klick navigiert weiterhin direkt zur
1579
- Übersicht (kein erzwungener 2-Klick). Der Reveal hängt weiter an `.is-open` (kein reines CSS-`:hover`),
1580
- `aria-expanded` läuft mit, `closeAllNavItems` verhindert zwei gleichzeitig offene Menüs. Escape schließt
1581
- jetzt auch ein rein per Hover geöffnetes Menü.
1582
- - **Topnav: Top-Level-Parents als Link zur Übersicht (Split „Link + Caret-Disclosure“)**: „Angewandte KI“,
1583
- „Leistungen“ und „Unternehmen“ sind jetzt echte `<a>`-Links auf ihre Übersichtsseite (`data-ep` = erstes
1584
- Submenü-Ziel), statt reiner Aufklapp-Buttons. Ein **separater Caret-`<button>`** (`.ep-nav-item-toggle`,
1585
- `aria-expanded`/`aria-controls`) öffnet das Submenü per Klick/Tastatur. UX: 1 Klick zur Übersicht statt
1586
- Umweg über den „Übersicht“-Dropdown-Eintrag; A11y: eigene Caret-Hit-Area ≥ 24 px (WCAG 2.5.8) + Fokus-Ring,
1587
- mobil Link + Caret in einer Zeile mit ≥ 44 px Tap-Fläche. Der nicht-farbige Aktiv-Unterstrich (WCAG 1.4.1)
1588
- sitzt jetzt am Parent-Link. Über alle Beispielseiten + Doku (`#gt-nav-topnav`) konsistent umgesetzt.
1589
- - **KI-Wissensbeitrag „Weiterlesen“: aktuelle Artikel-Cards** statt der alten `.ep-card`-Textkarten:
1590
- jetzt `.card.card-elevated` mit 16:9-Bild, `.pill`-Bereichslabel, `.card-title` und gepinntem
1591
- `.card-cta-link`, im `.layout-grid` (col-4) — identisch zur Beitragsübersicht (`ep-wb-uebersicht`).
1592
- - Topnav-Icon-Buttons und Hamburger auf 48 × 48 px (Touch-Target AAA, WCAG 2.5.5).
1593
- - Icon-Doku (`#sec-icons`): Solid-Bereichs-Glyphen vs. Outline-UI-Icons klargestellt
1594
- (vorherige „nur Outline“-Aussage war unzutreffend); Verweis auf die Icon-Bibliothek.
1595
- - **Headline/Display-Tokens fluid**: `--ty-headline-xs/sm/md` und `--ty-display-sm/md` nutzen
1596
- jetzt `clamp()` mit unitless Ratio-Zeilenhöhe (Mirror von `--ty-display-lg`). Desktop-Maxima
1597
- unverändert, Minima ≥ 20 px greifen am Phone. Typografie-Doku entsprechend aktualisiert.
1598
- - **Responsive-Überarbeitung der Beispielseiten** (3 Stufen: ≤ 520 px Phone · 521–768 px Tablet ·
1599
- > 768 px Desktop):
1600
- - Section-Innenabstand mobil 32 → 24 px (`--s6`); Hero-CTAs stapeln full-width erst ≤ 520 px
1601
- (vorher 768 px, lief auf Phablet zu breit).
1602
- - `.layout-grid` setzt beim Kollabieren die `col-N`-Spannweiten zurück, sodass ungleiche Splits
1603
- (z. B. `col-7`/`col-5`) gleich breit stapeln (vorher wurde das Bild schmaler).
1604
- - Mobile-Topnav: Burger als äußerstes rechtes Element (kein Positionssprung beim Öffnen).
1605
- - `.team-voice` mobil als Pull-Quote (Quote-Icon im linken Gutter statt eigener Zeile);
1606
- `.card`-`min-height` (2lh/3lh, nur für Grid-Gleichhöhe) entfällt einspaltig; `.ep-award-list`
1607
- als 2×2 statt versetztem Flex-Wrap; `.article-avatar-xl` mobil spaltenrelativ (max. 112 px).
1608
- - **Doku-Shell**: `.ds-sidebar` wird ≤ 1024 px ausgeblendet (Content full-width), damit die
1609
- Beispielseiten die echte Fensterbreite einnehmen und ihre viewport-`@media`-Queries korrekt
1610
- greifen — Voraussetzung für faithful Mobile/Tablet-Vorschau.
1611
- - **Effektive-Software-Hero**: neues, thematisch passenderes Motiv (Entwickler, Laptop-Sticker
1612
- Clean Code / Keycloak / Jakarta EE / Docker); web-optimiert (5000 px/5,1 MB → 2000 px/411 KB).
1613
- - **Typografie durchgängig rem-basiert (WCAG 1.4.4 „Resize Text“)**: Alle `--ty-*`-Tokens, die
1614
- 15 `.type-*`-Utilities und die hartcodierten Schriftgrößen in `base.css`/`components.css` von
1615
- `px` auf `rem` umgestellt (Basis `1rem = 16px`). Die fluiden `clamp()`-Tokens nutzen jetzt
1616
- `rem`-Min/Max und einen `rem + vw`-Mittel-Term (Zwei-Anker-Fluid-Kurve), sodass Schrift auch
1617
- auf die Browser-Standardschriftgröße reagiert, nicht nur auf Seiten-Zoom. `body` von `16px`
1618
- auf `1rem`. Desktop-Rendering pixelidentisch (Maxima unverändert). Ausnahme: SVG-Text
1619
- (`.bw-label-*`) bleibt px (skaliert übers `viewBox`). Token-Exporte via `npm run build:tokens`
1620
- regeneriert. Neuer Doku-Abschnitt zur medienübergreifenden Nutzung (Web/Print/PowerPoint, px↔pt).
1621
-
1622
- ### Fixed
1623
- - **Seminar-Sidebar in zwei Kästen geteilt**: Seit dem Umzug der Rahmendaten trug der eine Kasten zwei
1624
- Aufgaben, Nachschlage-Information und Kontaktformular, und die Formular-Überschrift saß als gefüllter
1625
- Bereichsbalken mitten im Element. Das las sich wie zwei zusammengeklebte Karten. Jetzt zwei Kästen mit
1626
- Abstand: oben Angebot und Rahmendaten, darunter das Formular mit seinem Balken dort, wo eine
1627
- Kartenüberschrift hingehört. Sticky-Verhalten und Formular unverändert.
1628
- - **Rohe `var(--n-200)`-Rahmen auf `var(--bd-strong)` umgestellt** (Seminar-Sidebar, adaptive
1629
- Kontaktseite, vier Stellen): `--n-200` flippt im Dark nicht und ergab dort einen grellen `#C9D3D3`-Rahmen.
1630
- `--bd-strong` ist im Light derselbe Ton, im Dark `#9DA8B6`. Entspricht der Konvention „nie rohes
1631
- `-100`/`-200` als Border“.
1632
- - **Falsches Bereichs-Icon auf fünf Verweiskarten**: Karten, die auf eine Bereichs-Übersicht
1633
- verlinken, tragen das Marken-Glyph des Bereichs (`ki-bot`, `es-window-check`, `wo-network`,
1634
- solid, eingefärbt über `.ep-card-icon.t-XX`). Zehn Karten hielten sich daran, fünf nicht: die
1635
- „Weiter im Thema“-Blöcke der Angebots-Detailseiten (Meetingflut, Scrum, LPM, Keycloak,
1636
- Keycloak-Erweiterungen) zeigten ein generisches Heroicon mit inline gesetztem
1637
- `stroke="var(--XX-700)"` statt des Glyphs. `icons/README.md` hatte die Regel nur für
1638
- `/leistungen` und die Landingpage notiert; sie hängt jetzt am Anlass statt an einzelnen Seiten,
1639
- mit ausdrücklicher Abgrenzung zu den thematischen Outline-Icons inhaltlicher Karten.
1640
- - **Hover bereichsgetönter Body-Links war im Dark Mode unlesbar**: In einem `data-accent`-Container
1641
- sprang `.body-link:hover` auch im Dark auf den `-800`-Ton, also dunkel auf dunkel. Gemessen:
1642
- `wo-800` auf `bg-page` = **1,17:1**, `es-800` = 1,29:1, weit unter der AA-Schwelle von 4,5:1. Der
1643
- Link verschwand beim Überfahren praktisch. Ursache: Die Light-Regel `[data-accent="wo"]
1644
- .body-link:hover` hat dieselbe Spezifität wie die Dark-Grundregel, und `components.css` wird nach
1645
- `dark-mode.css` geladen. Für den generischen `.body-link:hover` gab es den Dark-Override bereits,
1646
- für die drei Bereichsvarianten fehlte er. Jetzt ergänzt (`-100`, also heller statt dunkler, analog
1647
- zur bestehenden Regel): `wo-100` = 8,4:1, `es-100` = 7,6:1, `ki-100` entsprechend.
1648
- Ein systematischer Durchgang durch **alle** `:hover`-Regeln mit dunklen Tokens fand denselben Fehler
1649
- ein zweites Mal: der Chrome-Reset `[data-accent] .footer .body-link:hover` sprang im Dark auf
1650
- `co-800`, **1,97:1** → jetzt `co-100` = 14,3:1.
1651
- - **Chip-Hover lief im Dark Mode rückwärts**: Der Rand nutzt beim Überfahren die kräftigeren
1652
- `-500`/`-600`/`-700`-Töne. Im Dark sind das die dunkleren, der Rand wurde also schwächer statt
1653
- kräftiger und die Affordanz kehrte sich um (`ki` ruhend `-400` #BEE82D → hover `-700` #6B8208).
1654
- Kein AA-Verstoß (alle Werte blieben über der 3:1-Schwelle für Nicht-Text), aber falsch herum.
1655
- Dark-Overrides ergänzt, jeweils eine Stufe heller als der Ruhezustand: co `-200`, ki `-300`,
1656
- es `-200`, wo `-200`.
1657
- - **Gedrückter Chip war im Dark Mode kaum vom Grund zu unterscheiden**: Die Füllung `n-700` ergab
1658
- gegen `bg-page` nur **1,60:1**, der ausgewählte Zustand war praktisch nur am weißen Text erkennbar.
1659
- Er invertiert jetzt wie das Segmented Control, das dieses Muster im System bereits vorgibt
1660
- (`--seg-fill` `-300` mit `--seg-on` `-900`): helle Füllung `n-300` mit dunklem Text `n-900`,
1661
- Hover eine Stufe heller statt dunkler. Gemessen: Text auf Füllung 8,62:1, Füllung gegen Seite
1662
- 4,85:1. Light bleibt unverändert bei `n-700` mit weißem Text.
1663
- - **Drei weitere dunkel-auf-dunkel-Zustände**, gefunden durch den neuen Check statt durch Zufall:
1664
- der Hover-Rand des neutralen Chips (`n-400`, 2,79:1 und dunkler als der Ruhe-Rand → `n-200`) und
1665
- der Pause-Knopf im Logo-Karussell (`n-700`, **1,60:1** → `n-100`). Offen bleibt der gedrückte Chip:
1666
- seine Füllung ist im Dark eine sehr dunkle Fläche auf dunklem Grund. Der Zustand ist über den
1667
- weißen Text erkennbar, die sauberere Lösung wäre eine helle Füllung im Dark — das ist aber eine
1668
- Gestaltungsentscheidung und steht als dokumentierte Ausnahme im Check.
1669
- - **Vergleichstabelle (`.ep-compare-*`) Dark-Mode-Kontrast**: Summary, ✓-Glyph (`.ep-compare-yes`) und
1670
- - **Segmented Control trug in Bereichsformularen Corporate**: `.seg-option input:checked + label` war
1671
- fest auf `--co-700` verdrahtet. Ein KI-, ES- oder WO-Formular bekam dadurch mitten zwischen seinen
1672
- Feldern einen Corporate-Akzent und trug zwei Brand Areas gleichzeitig, entgegen der eigenen Regel
1673
- „ein Formular gehört zu genau einer Brand Area“.
1674
- - **Links im Buchungsformular blieben Corporate**: Die Inhouse-Anfrage im Helper und die beiden
1675
- Consent-Links standen auf `--co-700`, obwohl der Abschnitt genau zwei bewusste Corporate-Reste
1676
- ausweist (Fokusring, Feldrahmen im Fokus). Ursache war der fehlende Akzent-Scope: die Demo hängt
1677
- in einer Doku-Karte, nicht in einer `.ep-page`. Der Wrapper trägt jetzt `data-accent="ki"`.
1678
- - **Consent-Links waren `<span>` statt Anker**: Die verlinkten Datenschutz- und Bedingungs-Hinweise in
1679
- den Einwilligungs-Checkboxen waren nicht per Tab erreichbar und wurden von Screenreadern nicht als
1680
- Link angesagt, obwohl genau dieser Text die Grundlage der Einwilligung ist. Jetzt durchgehend echte
1681
- `<a class="body-link">`: Buchungsformular (2×), beide Newsletter-Varianten und die Event-Anmeldung,
1682
- die als dritte Variante ein `<span class="t-es">` mit `text-decoration:underline` trug. Als
1683
- **systemweite Regel** dokumentiert (Inputs & Forms, Zeile „Link im Label“, plus Do/Don't-Paar und
1684
- CONTRIBUTING § 8); die A11y-Zeile des Buchungsformulars verweist darauf. Die Übergabe-Tabelle
1685
- „Für die Umsetzung“ fordert zusätzlich echte Ziele für die Rechtstexte (die Demos tragen `href="#"`)
1686
- und ein Öffnen ohne Formularverlust.
1687
- - **Bereichsformulare tönten ihren Consent-Link nicht**: Neben dem Buchungsformular betraf das die
1688
- Event-Anmeldung (Sektion `#ev-anmeldung` trägt jetzt `data-accent="es"`) und die adaptive
1689
- Kontaktseite, wo `applyKontaktContext()` Header, Button und Häkchen umtönte, den Datenschutz-Link
1690
- im Consent-Label aber corporate ließ. Das Attribut sitzt dort am `<form>`, nicht an der Karte:
1691
- Telefon, E-Mail und Maps-Link daneben sind Unternehmens-Kontaktdaten und bleiben Corporate.
1692
- Bei `co` wird es entfernt, Corporate ist der Default.
1693
- - **Bereichs-CTAs aus der Doku verloren ihren Kontext**: Der globale Anker-Handler rief
1694
- `activateExamplePage(epKey)` ohne Kontext-Objekt, anders als der `ep-page`-Handler. Ein Link mit
1695
- `data-k-bereich`/`data-k-anliegen` außerhalb einer Beispielseite (etwa „Inhouse-Termin anfragen“
1696
- im Buchungsformular) landete deshalb auf der neutralen Kontaktseite: keine Tönung, kein
1697
- vorbelegtes Thema, kein Anliegen. Beide Pfade geben den Kontext jetzt gleich weiter.
1698
- - **`.helper` verfehlte AA im Dark Mode**: Hilfetexte unter Feldern nutzten `--tx-muted`, das auf
1699
- `--bg-surface` nur 4,1:1 erreicht (die Kontrast-Tabelle dokumentiert das dort ausdrücklich als
1700
- „Large Text / Non-Text“). Umgestellt auf `--tx-secondary`: 6,3:1 Light, 4,9:1 Dark. Betrifft alle
1701
- Formulare mit Hilfetext.
1702
- - **Fehlerrahmen an Select und Textarea**: `.field.has-error` färbte nur `input` rot, ein fehlerhaftes
1703
- Pflicht-Select blieb optisch unmarkiert. Regel um `select` und `textarea` erweitert.
1704
- - **`--c-error` verfehlte AA im Dark Mode auf `bg-surface`**: `#FF8E8E` war nur gegen `--bg-page`
1705
- geprüft (4,9:1). Inline-Fehlertext sitzt aber fast immer in einer Formularkarte auf `--bg-surface`,
1706
- dort waren es 4,0:1 bei 12 px. Angehoben auf `#FFA5A5`: 5,9:1 auf bg-page, 4,75:1 auf bg-surface.
1707
- Betrifft alle Fehlermeldungen, Required-Sternchen und ✕-Marker im Dark Mode.
1708
-
1709
- der „Pro“-Header (`thead .ep-compare-pro`) nutzten rohes `co`/`ki`-`800` (`--ki-800` #475705), das im
1710
- Dark nicht mitflippt → dunkel-auf-dunkel (Core-Spalte auf `bg-page`, Pro-Spalte auf `--ki-50` #2A3411,
1711
- Häkchen faktisch unsichtbar). Dark-Override ergänzt: ✓ und Pro-Header auf `--ki-100`, Summary auf
1712
- `--ki-200` (Hover `--ki-100`), analog zum bestehenden `.ep-feature-icon`/`.ep-tier-label`-Muster.
1713
- Light-Mode unverändert.
1714
- - **Hover-Farben auf `--tx-primary`-Text im Dark (`.footer-link`, `.article-toc`)**: Der Hover sprang auf
1715
- `--co-700`, das im Dark nicht flippt → der Link wurde beim Hovern dunkler statt heller (dunkel-auf-dunkel).
1716
- Dark-Override auf `--co-200` ergänzt (`.footer-link:hover`, `.article-toc-summary:hover`,
1717
- `.article-toc-list a:hover`), konsistent mit `.body-link:hover`. Light-Mode unverändert.
1718
- - **`.cta-dl[data-area="co"]` Eyebrow-Kontrast (Light)**: `co-600` (#009E9E, 3,28:1, Uppercase-Label < AA)
1719
- auf `co-700` (5,5:1) → jetzt konsistent mit ki-800/es-700/wo-700. Dark-Override (`co-200`) unverändert.
1720
- - **Fokus-Ring global kontraststark (`--focus-ring` / `--focus-aa`)**: sichtbarer Ring im Light-Mode
1721
- von `co-500` (#00BEBE, nur 2,3:1 auf Weiß) auf `co-700` (#007575, 5,5:1) → erfüllt die 3:1-Schwelle
1722
- für Fokus-Indikatoren (WCAG 2.4.11). Gilt systemweit, da alle `:focus-visible`-Ringe diese zwei Tokens
1723
- nutzen. Dark-Mode unverändert `co-500` (dort 4,7:1 auf dunklem Grund; `co-700` wäre dunkel-auf-dunkel).
1724
- - **Doku-Sidebar Struktur-Semantik**: Gruppen-Labels (`.nav-section`) sind jetzt `<h2>` (Screenreader-
1725
- Outline der Navigation), und die Einträge jeder Gruppe liegen in `<ul class="nav-list">` mit
1726
- `aria-labelledby` auf das Gruppen-Heading (Ansage „Gruppe, Liste, N Einträge“). Reine Semantik-/
1727
- Markup-Änderung, Optik und JS unverändert.
1728
- - **Doku-Sidebar (`.nav-item`) barrierefrei**: (1) aktiver Eintrag-Text von `co-500` (#00BEBE auf
1729
- `co-50` nur 2,07:1) auf `co-700` (4,95:1) → WCAG 1.4.3; Akzentbalken auf `co-600` (3,28:1, WCAG 1.4.11).
1730
- (2) `aria-current="page"` am aktiven Eintrag (statisches Markup + synchron in `activateSection`),
1731
- sodass der aktive Bereich nicht mehr nur farblich, sondern programmatisch erkennbar ist (4.1.2).
1732
- Zustand zusätzlich über `font-weight` (nicht nur Farbe, WCAG 1.4.1). Dark-Mode: aktiver Text/Balken
1733
- auf `co-200`/`co-300` (auf dem dort fast schwarzen `co-50`).
1734
- - **Bereichs-Chips (`.chip[data-area="co"]`) Rahmenkontrast**: Corporate-Rahmen von `co-500`
1735
- (#00BEBE, nur 2,3:1 auf Weiß) auf `co-600` (#009E9E = 3,28:1) angehoben → erfüllt die
1736
- Nicht-Text-Schwelle WCAG 1.4.11 (Default + Hover). Bleibt heller als der `co-700`-Text, das
1737
- Zwei-Ton-Prinzip (Rahmen 3:1 / Text 4,5:1) bleibt erhalten. ki/es/wo lagen bereits ≥ 3:1;
1738
- Dark-Mode (`co-300`, 4,8:1) unverändert.
1739
- - **Light/Dark-Umschalter (`.theme-bar`) barrierefrei & konsistent**: (1) aktive Fläche von
1740
- `co-500` auf `co-700` umgestellt, weißer Text erreicht AA (2,3:1 → 5,5:1); (2) Unicode-Glyphen
1741
- (☀ / ◑) durch dieselben Heroicons-SVGs (Sonne / Sichelmond) wie der Topnav-Umschalter ersetzt,
1742
- je `aria-hidden="true"`; (3) `aria-pressed` ins statische Markup (vorher nur per JS).
1743
- - **Slider-Wertanzeige (`.field-slider-output`) barrierefrei**: (1) Textfarbe von der hellen
1744
- Thumb-Farbe (`--sl-color`) entkoppelt (neues `--sl-text`), Wert nutzt jetzt dunklere Töne
1745
- `co-700`/`ki-800`/`es-700`/`wo-700` → AA im Light-Mode (co 2,3:1, wo 3,3:1, ki 4,35:1 → 5,5–9,6:1),
1746
- Thumb bleibt in Markenfarbe; (2) `aria-valuetext` an allen Slidern (initial + im `oninput`
1747
- synchronisiert), damit Screenreader „50.000 €“ / „60 %“ statt der Rohzahl ansagen.
1748
- - **Beispielseiten-Chips (`.ep-tab`) barrierefrei**: (1) aktiver Chip von `co-500` auf `co-700`
1749
- umgestellt, weißer Text erreicht damit AA (2,3:1 → 5,5:1); (2) Touch-Target auf `min-height:44px`
1750
- bzw. 44 × 44 px beim Aufklapp-Toggle (WCAG 2.5.5, analog `.btn`); (3) Rahmen von `--n-200` auf
1751
- das modusabhängige `--field-border` (WCAG 1.4.11: Light-Mode 1,5:1 → 3,9:1, Dark unverändert 5,8:1).
1752
-
1753
- ## [0.1.0] - 2026-06-25
1754
-
1755
- ### Added
1756
- - Onboarding-/Governance-Doku: `README.md`, `CONTRIBUTING.md` (Konventionen),
1757
- `LICENSE`, dieses `CHANGELOG.md`, `docs/GETTING-STARTED.md`.
1758
- - npm-Paketierung (`@conciso/design-system`, `package.json` mit `exports`/`files`).
1759
- - Token-Export `tokens/tokens.json` · `.scss` · `.js` (generiert aus `tokens.css`
1760
- + Dark-Overrides, `var(...)` aufgelöst) via `scripts/build-tokens.mjs`.
1761
- - Gebündeltes `dist/conciso-ds.css` (korrekte Ladereihenfolge) via
1762
- `scripts/bundle-css.mjs`. `npm run build` erzeugt beides.
1763
- - **Self-Host-Fonts** (DSGVO): `fonts/` (Montserrat + Libre Baskerville, woff2,
1764
- Subsets latin + latin-ext) + `css/fonts.css` (`@font-face`, `font-display:swap`),
1765
- inkl. OFL-Lizenztexte. Doku-Site und Bundle nutzen jetzt lokale Fonts statt
1766
- Google-CDN — verifiziert: 0 Anfragen an googleapis/gstatic.
1767
-
1768
- ### Changed
1769
- - Repo-Hygiene: Doku-Site vom konsumierbaren Kern getrennt. `index.html`,
1770
- `main.js` und Bilder liegen jetzt unter `docs/` (Bilder in `docs/assets/images/`);
1771
- Pfade in `docs/index.html` entsprechend angepasst. Der Kern (`css/`, `dist/`,
1772
- `tokens/`) bleibt im Root.
1773
- - Distribution: **intern/proprietär** statt Registry-Publish. `package.json`
1774
- `license: "UNLICENSED"` + `private: true`; `publishConfig` und der
1775
- Release-Workflow (`.github/workflows/release.yml`) entfernt. Nutzung über
1776
- eingebundenes/kopiertes `dist/conciso-ds.css` oder gepinnte Git-Tags
1777
- (`npm install github:conciso/conciso-design-system#vX.Y.Z`). `LICENSE` auf
1778
- einen kurzen internen Hinweis eingedampft (Font-OFL-Verweise bleiben).
1779
-
1780
- ---
1781
-
1782
- Frühere Arbeit (vor formaler Versionierung) ist in der Git-Historie
1783
- dokumentiert: Aufbau der Foundations (Tokens, Typografie 16/14/12, Spacing,
1784
- Elevation), Komponentenbibliothek, vollständiger Light/Dark-Mode mit
1785
- WCAG-AA-Kontrasten, Beispielseiten und die navigierbare Doku-Site.
1786
- Die erste getaggte Version markiert den Start der paketierten Distribution.