# Design-Tokens-Report — RentenCheck

Was aus den extraETF-SingleFile-Snapshots (`extraetf_design_input/`)
gefördert wurde, was daraus in `assets/rentencheck.css` / `assets/fonts.css`
geworden ist und was approximiert werden musste.

Quellen:

| Datei | Größe | Rolle im Mining |
|---|---|---|
| `avd.html` | 14.7 MB | Marketing-Landingpage + Altersvorsorge-Rechner (Slider, CTA-Pills, Type-Scale, Radien, Spacing, Shadows, `.tap-effect`, `.section-container`, `.max-w-text`) — der Großteil der Bytes ist eingebetteter YouTube-Ballast (`--yt-*`), ignoriert |
| `home.html` | 1.2 MB | Login; identische Tokens, Fonts mit `local("sans-serif")`-Fallback (nicht verwendet) |
| `register.html` | 391 KB | Registrierung: **vollständiger `:root`-Tokenblock** (973 Custom Properties, Byte 1811–52539), Feld-/Button-Muster der App, „BigStepper" |

Extraktion per `grep`/`python` mit Byte-Offsets — die 14.7-MB-Datei wurde nie als
Ganzes gelesen.

---

## 1. Fonts (`assets/fonts.css`, 186 KB)

Vier `@font-face`-Blöcke, base64-woff2 **wörtlich** aus `avd.html`
(Offsets 59678 / 104079 / 152952 / 203377). Jeder Block auf `wOF2`-Signatur und
Glyphenabdeckung geprüft (fontTools):

| family | deklariert | interner Name | woff2 | Glyphen | ä ö ü Ä Ö Ü ß € |
|---|---|---|---|---|---|
| `Montserrat` | 300 | Montserrat Leicht | 36 KB | 415 | ✓ |
| `Montserrat` | 400 | Montserrat | 33 KB | 415 | ✓ |
| `Montserrat` | 600 | Montserrat Halbfett | 37 KB | 415 | ✓ |
| `Montserrat ExtraBold` | **600 700** | Montserrat ExtraBold Fett | 34 KB | 405 | ✓ |

- **Kein 700-Schnitt von `Montserrat`** in irgendeinem Snapshot. extraETF nutzt
  `font-weight:600` als schwerste reguläre Stufe; `.h2`–`.h5` sind 600.
- `Montserrat ExtraBold` liegt als **`Fett`** (700-Design) vor, wird von extraETF aber
  als `font-weight:600` deklariert, während `.text-display*` `font-weight:700`
  setzt → Faux-Bold. Hier als Range `font-weight:600 700` deklariert, damit
  `.display1/2/3` (700) die echte Datei bekommt.
- `home.html`/`register.html` deklarieren `src:local("sans-serif"),url(...)` —
  das würde die lokale Systemschrift gewinnen lassen. Deshalb wurden die
  Blöcke aus `avd.html` genommen (nur `url(...)`).
- Verifiziert im Headless-Chromium: alle vier Faces `status:"loaded"`.

---

## 2. Farben — gemined, 1:1 übernommen

Der Tokenlayer ist konsequent `-light`/`-dark`-gepaart. Übernommene Werte:

### Brand (grün)

| Rolle | light | dark | Quelle |
|---|---|---|---|
| default | `#2196f3` | `#64b5f6` | `--color-bg-action-primary-default-*` |
| hover / pressed | `#04cf00` | `#3eda00` | `--color-bg-action-primary-hover-*` |
| disabled | `#c0fc7b` | `#086f00` | `--color-actionPrimary-disabled-*` |
| vibrant | `#009811` | `#bdfe6b` | `--color-bg-accent-brand-vibrant-*` |
| vivid | `#004a10` | `#e2feba` | `--color-bg-accent-brand-vivid-*` |
| subdued | `#c0fc7b` | `#004a08` | `--color-bg-accent-brand-subdued-*` |
| muted (Tint-Fläche) | `#ecffcf` | `#002303` | `--color-bg-accent-brand-muted-*` |
| tint (Selektion) | `#2196f31a` | `#64b5f61a` | `--color-bg-selection-default-*` |
| Text auf Surface | `#006f13` | `#64b5f6` | `--color-onSurface-accent-brand-*` |
| Border Selektion | `#2196f366` | `#64b5f666` | `--color-border-selection-default-*` |
| Text **auf** Brand | `rgba(2,3,4,.91)` (beide) | | `--color-static-onSurfaceInv-default-*` |

Weitere Brand-Stufen im Snapshot (nicht alle in die CSS übernommen):
`#dafdae`, `#86fa41`, `#006f13`, `#20bb00`, `#aafe4a`, `#d1fe95`, `#e2feba`.

### Status

| Rolle | light | dark | Quelle |
|---|---|---|---|
| positive (Fläche) | `#21dc52` | `#64b5f6` | `--color-bg-accent-pos-default-*` |
| positive muted | `#e9fde5` | `#002303` | `--color-bg-accent-pos-muted-*` |
| positive Text | `#006f13` | `#aafe4a` | `--color-onTinted-status-success-default-*` |
| info/highlight | `#00c7ea` | `#00ddfa` | `--color-feedbackHighlight-default-*` |
| info muted | `#e1fdff` | `#001a29` | `--color-bg-accent-blue-muted-default-*` |
| info Text | `#00617e` | `#9ef4fb` | `--color-onTinted-status-highlight-default-*` |
| warning | `#fed200` | `#fed200` | `--color-bg-status-warning-default-*` |
| warning muted | `#fcf8d3` | `#2f1400` | `--color-bg-accent-yellow-muted-default-*` |
| warning Text | `#885700` | `#fee564` | `--color-onTinted-status-warning-default-*` |
| negative | `#ff2f3e` | `#ff2f3e` | `--color-feedbackCritical-default-*` |
| negative strong (Action) | `#d51b24` | `#d51b24` | `--color-actionCritical-default-*` |
| negative hover | `#ff2f3e` | `#a80f12` | `--color-actionCritical-hover-*` |
| negative muted | `#fff3f5` | `#310702` | `--color-bg-accent-neg-muted-default-*` |
| negative Text | `#7c0604` | `#ff7a84` | `--color-onTinted-status-critical-default-*` |
| negative Border | `#ffabb1` | `#a80f12` | `--color-border-status-critical-default-*` |

Weitere Akzentfamilien im Snapshot, im Prototyp **nicht** genutzt: orange
`#ff7407`, purple `#b15ff6`, green `#21dc52` (separat von „pos"), sowie die
Produkt-Tokens (`premium`, `savings`, `vipGold/Silver/Active`) und
`decorativePrimary…Quaternary`.

### Surfaces

Leiter `lower < low < default < high < higher` (in beiden Themes wird es nach
oben heller):

| Stufe | light | dark |
|---|---|---|
| lower | `#e3e3e3` | `#000000` |
| low | `#ededee` | `#050505` |
| default | `#f8f8f8` | `#0c0e0e` |
| high | `#fafafa` | `#18191a` |
| higher | `#ffffff` | `#313434` |

Zusätzlich die numerische Leiter `surface-0…50` (light: `#f8f8f8`, `#ededee`,
`#dbdcdd`, `#313233`, `#191919`, `#050505` — ab 30 invertiert sie, das sind
Inverted-Surfaces) sowie `*Alpha`/`*AlphaInv`-Varianten.

Neutrale Alpha-Füllungen (Felder, Slider-Tracks, Chips) — 1:1 übernommen:

| Rolle | light | dark |
|---|---|---|
| `--rc-neutral` | `rgba(28,46,48,.05)` | `rgba(213,227,241,.09)` |
| `--rc-neutral-hover` / `-high` | `rgba(15,15,15,.09)` | `rgba(234,250,250,.19)` |

### Text

| Rolle | light | dark | Quelle |
|---|---|---|---|
| default | `rgba(2,3,4,.91)` | `rgba(254,254,254,.89)` | `--color-onSurface-default-*` |
| emphasized | `#050505` | `#f8f8f8` | `--color-onSurface-emphasized-*` |
| subdued | `rgba(1,8,8,.71)` | `rgba(250,253,254,.8)` | `--color-onSurface-subdued-*` |
| muted | `rgba(2,14,15,.62)` | `rgba(244,248,254,.51)` | `--color-onSurface-muted-*` |
| disabled | `rgba(2,8,9,.28)` | `rgba(239,254,254,.29)` | `--color-onSurface-disabled-*` |
| inverted | `#edeeee` | `rgba(2,3,4,.91)` | `--color-onSurfaceInv-default-*` |

### Borders / Overlay

| Rolle | light | dark |
|---|---|---|
| default | `rgba(15,15,15,.09)` | `rgba(234,250,250,.19)` |
| emphasized | `rgba(6,22,23,.12)` | `rgba(239,254,254,.29)` |
| moderate | `rgba(1,15,16,.19)` | `rgba(231,251,251,.4)` |
| strong | `#050505` | `#f8f8f8` |
| blanket (Modal) | `rgba(2,14,15,.62)` (beide) | |

### Sekundäre (schwarze) Action

`--color-actionSecondary-default-*`: light `#191919` / dark `#f8f8f8`,
hover light `#050505` / dark `#edeeee` → `.btn-secondary`.

---

## 3. Maße — gemined, 1:1 übernommen

**Radien** (`.rounded-*`): `sm .125rem`, `md .375rem`, `lg .625rem`,
`cardDefault .875rem`, `modalDefault 1.375rem`, `full 62.4375rem`.

**Control-Höhen** (`h-sizing-ui-control-*`): `xs 1.75rem`, `sm 2.25rem`,
`md 2.75rem`, `lg 3.25rem`. Dazu die Marketing-CTA-Höhe `min-h-[52px]`.

**Spacing-UI** (`*-spacing-ui-*`): `xs .5rem`, `sm .75rem`, `md 1rem`,
`lg 1.25rem`, `xl 1.5rem`, `2xl 2rem`.

**Shadows**: `sm = 0 1px 2px 0 rgb(0 0 0/.05)`,
`xl = 0 20px 25px -5px rgb(0 0 0/.1), 0 8px 10px -6px rgb(0 0 0/.1)`.
Mehr gibt es im Snapshot nicht — extraETF ist praktisch schattenfrei.

**Motion**: `.tap-effect{transition:all .7s cubic-bezier(.25,1,.5,1)}` +
`:active{transform:scale(.975);transition-duration:.2s}`.

**Layout**: `.section-container{padding-inline:calc(.5rem + 2rem)}`, ab `64rem`
`2rem`; `.max-w-text{max-width:720px;text-wrap:pretty}`;
`.page-container` responsive `40/48/64/80/96rem`; `max-w-screen-2xl = 96rem`.

**Type-Scale** (alle mit `font-feature-settings:"tnum"`):

| Klasse | family | size / line-height | weight | Extras |
|---|---|---|---|---|
| `display1` | Montserrat ExtraBold | `6.875rem / 6.25rem` (≥48rem) | 700 | uppercase |
| `display2` | Montserrat ExtraBold | `2.75/2.5rem` → `4.6875/4.25rem` | 700 | uppercase, ls `.0625rem` → 0 |
| `display3` | Montserrat ExtraBold | `2.75/2.25rem` → `3.1875/3rem` | 700 | uppercase |
| `h2` | Montserrat | `1.8125/2.25rem` → `2.1875/2.5rem` | 600 | ls `-.75%` → `-1%` |
| `h3` | Montserrat | `1.5/1.75rem` | 600 | ls `-.5%` |
| `h4` | Montserrat | `1.1875/1.25rem` | 600 | |
| `h5` | Montserrat | `1/1.25rem` | 600 | |
| `h6` | Montserrat | `.75/1rem` | 400 | |
| `copy-lg` | Montserrat | `1.1875/1.75rem` | 400 | |
| `copy-md` | Montserrat | `1/1.5rem` | 400 | |
| `copy-sm` | Montserrat | `.875/1.25rem` | 400 | |
| `copy-xs` | Montserrat | `.75/1rem` | 400 | ls `.02rem` |
| `label-md` | Montserrat | `1/1.25rem` | 400 | |
| `label-sm` | Montserrat | `.875/1rem` | 400 | ls `.02rem` → 0 ab 48rem |
| `label-xs` | Montserrat | `.75/1rem` | 400 | ls `.02rem` |

`html{font-family:Montserrat,sans-serif;line-height:1.5}`.

---

## 4. Komponentenmuster — aus dem Markup rekonstruiert

- **Primär-CTA (Marketing)** `avd.html`: `rounded-full min-h-[52px]
  px-spacing-ui-md pb-[13px] pt-[10px] text-copy-lg font-semibold tap-effect
  bg-action-primary-default text-static-onSurfaceInv-default` → `.btn`.
- **Sekundär/inaktiv (Tab)**: `bg-action-default-tinted-default` +
  `hover:bg-action-default-tinted-hover` → `.btn-ghost`.
- **App-Button** `register.html`: `rounded-sm py-3 min-h-sizing-ui-control-md
  px-5 text-label-md !font-bold` — die App nutzt fast eckige Buttons. Für den
  Prototyp wurde bewusst durchgehend die **Marketing-Pill** gewählt (Landing +
  Wizard sind der Hauptteil der Journey); `.btn-md` / `.btn-sm` liefern die
  App-Höhen.
- **Feld** `register.html`: `border border-solid border-transparent
  bg-neutral-default hover:bg-neutral-hover rounded-lg px-spacing-ui-md
  py-spacing-ui-sm h-sizing-ui-control-md text-copy-md
  placeholder-onSurface-muted placeholder-opacity-50 transition-all
  duration-200 ease-out` → `.field` / `.input` (1:1).
- **Slider** (Altersvorsorge-Rechner, `avd.html` ~Byte 534000): 8-px-Track
  `rounded-full bg-neutral-high-default`, Brand-Fill `bg-accent-brand-default`,
  Marker `bg-accent-brand-vibrant-default` (18×8 px, „150 € Fördergrenze"),
  Thumb `h-5 w-5 rounded-full border-default bg-onSurface-primary shadow-sm`,
  Fokus `peer-focus-visible:ring-2 ring-border-selection-default`. Das echte
  `input[type=range]` ist `opacity-0` über gestapelten `<span>`s.
  → als **gestyltes `input[type=range]`** (`.slider`, `--pct`-Fill) nachgebaut;
  Track-, Fill-, Thumb- und Ring-Farben identisch.
- **Eyebrow**: `text-label-sm mb-4 uppercase tracking-[0.16em]
  text-onSurface-muted` → `.eyebrow` (1:1).
- **Badge**: `inline-flex rounded-md px-spacing-ui-xs pt-[5px] pb-[6px]
  text-label-xs min-h-sizing-ui-control-xs bg-action-primary-default
  text-static-onSurfaceInv-default` → `.badge` / `.badge-brand` (1:1).
- **Link**: `border-b border-actionPrimary-default hover:border-actionPrimary-hover`
  — Unterstreichung in Brand-Grün, Text in Textfarbe → globales `a`.
- **Stepper** (`data-testid=bigOnboardingStepper`): 48-px-Kreise (`h-12 w-12`),
  `border-2`, vertikaler Connector (`border-r-2`), erledigt =
  `bg-feedbackHighlight-default` (blau!) mit invertiertem Text, Titel `text-h4`,
  Untertitel `font-semibold text-copy-sm text-onSurface-tertiary`.
  → `.stepper` / `.step.is-done` (blau) / `.step.is-active` (brand, ergänzt).
- **Card**: `overflow-hidden rounded-cardDefault shadow-sm bg-surface-higher`,
  Trennlinien `divide-y divide-[#aaaaaa]/20` → `.card` / `.card-divided`.
- **Fokus**: `focus-visible:outline-1 focus-visible:outline-border-strong
  focus-visible:outline-offset-2`. Achtung: Die im Markup referenzierte Klasse
  `focus:outline-button-light` / `dark:focus:outline-button-dark` ist in **keinem**
  der drei Snapshots als CSS-Regel enthalten (Tailwind-Purge-Lücke) — der
  Button-Fokusring war also nicht minebar.

---

## 5. Approximiert / erfunden — und warum

| Element | Entscheidung | Grund |
|---|---|---|
| `--rc-surface` = `#ffffff` / `#18191a` | Card-Fläche | extraETF nutzt `surface-higher`; in Dark wäre das `#313434` und liest sich als hellgraue Box statt als Karte. `#18191a` (= `surface-high-dark`) trennt Karte und Seite sauberer. |
| `--rc-surface-high` = `#f8f8f8` / `#313434` | verschachtelter Block | in Light bewusst `surface-default` als Tint **innerhalb** einer weißen Karte. |
| `--rc-surface-sunken` = `#ededee` / `#050505` | Tabellenkopf, Wells | `surface-low-*`, Rolle ergänzt. |
| Fokusring `2px solid var(--rc-border-strong)`, Offset `2px` | global | aus `focus-visible:outline-*` abgeleitet; Breite von 1 auf 2 px erhöht (Sichtbarkeit im Prototyp). Die Button-Variante fehlte im Snapshot. |
| `.display1` Mobile-Wert `3.5rem/3.125rem` | | im Snapshot existiert nur die `≥48rem`-Regel (`6.875rem`); die Basisregel wurde von Tailwind gepurged. Skaliert nach dem `display2`-Verhältnis. |
| Dark-Shadows `rgb(0 0 0/.5)` bzw. `/.6` | | im Snapshot gibt es keine Dark-Shadow-Tokens (extraETF deckt Elevation über Surfaces ab). |
| `.option-tile` (Antwort-Tiles) | neu | „eine Frage pro Screen" existiert bei extraETF nicht. Gebaut aus vorhandenen Primitiven: `rounded-cardDefault`, `bg-surface`, `border-default`, Selektion = `bg-selection-default` + `border-selection`, `tap-effect`. |
| `.why-box` („Warum fragen wir das?") | neu | `bg-accent-brand-muted-default` + `rounded-cardDefault`; Titel in `onSurface-accent-brand`. |
| `.find-hint` („Wo finde ich das?") | neu | `bg-neutral-default` + `rounded-lg`, `copy-sm`, subdued. |
| `.wizard-progress` | neu | 4-px-Balken, `bg-neutral-high` Track + Brand-Fill; Timing aus `.tap-effect`. |
| `.warn-box` (Zulagen-Rückzahlung, verpflichtend) | neu | `bg-status-warning`-Tint (`#fcf8d3` / `#2f1400`) + 1 px `#fed200`-Rahmen, Titel in `onTinted-status-warning-default`. `.crit-box` analog in Rot für „nicht kündigen". |
| `.ampel` | neu | drei 0.6-rem-Punkte in einem `bg-neutral`-Pill; aktiver Punkt aus `negative` / `warning` / `positive`, inaktiv `text-disabled`. |
| Konfidenz-Badges | neu | Hoch = `positive-muted`/`positive-text`, Mittel = `warning-muted`/`warning-text`, Niedrig = `neutral`/`text-muted`. Klassen `.badge-hoch/-mittel/-niedrig`. |
| `.result-hero .num` | neu | `Montserrat ExtraBold 700`, `clamp(3.25rem,14vw,6.875rem)` — Obergrenze = gemineter `display1`-Wert; `tnum` erzwungen. |
| `.upload-drop` | neu | 2 px dashed `border-moderate`, Hover → `border-brand` + `brand-tint`. |
| `.toggle` | neu | 44×24-px-Pill (`neutral-high` → `brand`), Knopf auf `surface` mit `shadow-sm`. |
| `.tbl` | neu | extraETF hat in den Snapshots keine Datentabelle; gebaut aus `border-default`, `surface-sunken`-Kopf, `neutral`-Hover, `tnum`-Zahlenspalten. |
| `.wizard-shell` = 640 px | neu | Wizard-Spalte; zwischen `max-w-[512px]` (Registrierung) und `max-w-text` (720 px). |
| `--rc-container` = `96rem` | | `max-w-screen-2xl`; `.section-container` bekommt es fest, da im Snapshot per Utility-Klasse kombiniert. |
| Theme-Schalter `[data-theme]` | | extraETF toggelt `.dark` auf dem Root (`:is(.dark .foo)`). Der Prototyp folgt der Jester-Konvention `data-theme` + `prefers-color-scheme`-Fallback. |

Nicht übernommen (im Snapshot vorhanden, für die Journey irrelevant):
`decorative*`-Paletten, Produkt-Tokens (`vipGold` …), `gradient-*`
(z. B. `--color-gradient-accent-brand-strong-light:
linear-gradient(#2196f3,#7df584 8%,#ceffca 17%,#e9fde5 34%,#f8f8f800)`),
`onSurfaceInverted*`/`borderInverted*`, `--color-clear-*`, Orange/Purple.

---

## 6. Prototyp-Chrome (nicht extraETF, aber in derselben Datei)

`rentencheck.css` enthält zusätzlich die Chrome-Klassen, die `assets/app.js`
injiziert bzw. `index.html` nutzt — sie müssen hier liegen, weil es **keine
Per-Page-`<style>`** geben darf:

`#proto-bar` · `.pb-brand/.pb-scr/.pb-sp` · `.seg` (+ `[aria-pressed="true"]`) ·
`.frame` / `body.frame-mobile` / `body.frame-embed` ·
`.rc-head/.rc-mark/.rc-name` (Wortmarke, `.rc-mark` ist ein CSS-Häkchen im
Brand-Grün — noch kein echtes Logo-Asset) · `.portal` + `nav.side` + `.main` ·
`.portal-topbar/.nav-toggle/.side-backdrop/body.nav-open` (Off-Canvas <48rem) ·
`[data-icon] svg` (1.125 rem).

Alle davon nutzen ausschließlich `--rc-*`-Tokens, sind also automatisch
theme-fähig.

Bare `h1`–`h6` bekommen `font-weight:600` + die Größen aus der Type-Scale, damit
Platzhalterseiten ohne `.h2`-Klasse nie einen Faux-Bold-Montserrat rendern (es gibt
keinen 700-Schnitt).

> **Offener Punkt aus einem anderen Foundation-Task:** `assets/stepnav.js`
> injiziert einen eigenen `<style>`-Block mit hartkodierten KrediFin-Farben
> (`#5534FF`, `#2a2d42`). Das widerspricht „ONE shared stylesheet" und muss auf
> `--rc-*`-Klassen in `rentencheck.css` umgestellt werden.

## 7. Selbsttest

`prototype/_archive/_tokentest.html` (nach QA archiviert) rendert jede Komponentenklasse in beiden Themes
(inkl. Token-Swatch-Grid). Geprüft im Headless-Chromium (Playwright MCP):

- `fonts.css` → 4 Regeln, alle vier Faces `loaded`; `rentencheck.css` → 273 Regeln
  zum Zeitpunkt der Erstmessung, keine verworfene Regel, keine Konsolenfehler
  (außer fehlendem `favicon.ico` bzw. dem noch nicht laufenden `/api/comments`).
  **Stand 26.07.2026 nach QA- und Redesign-Layer: 411 Regeln im Dokument**
  (davon ~48 aus dem `<style>`-Block, den `comments.js` zur Laufzeit injiziert —
  die „ein Stylesheet"-Regel gilt für Autorenzeit, nicht für dieses Widget).
  Wichtig: QA- und REDESIGN-Layer haben **keine neuen `--rc-*`-Tokens** angelegt
  (weiterhin 123 Token-Deklarationen) — beide Layer arbeiten rein auf Klassenebene.
  Deshalb bleiben die Swatch- und Maß-Zähler auf `design-system.html` unverändert.
- `.display2` bekommt tatsächlich `"Montserrat ExtraBold"` @ 700.
- `body` erbt `font-feature-settings:"tnum"`.
- Light/Dark über `data-theme` verifiziert (`--rc-bg` `#f8f8f8` ↔ `#0c0e0e`).
- Kein horizontaler Overflow bei 420 px, 780 px und 1280 px.
- Keine `http(s)://`-Referenz in `fonts.css`, `rentencheck.css` oder
  `_archive/_tokentest.html` — komplett self-contained, lauffähig unter `file://`.

## 8. Redesign-Layer (REDESIGN.md, 2026-07-26)

Nach dem User-Verdikt zu v1 kam in `rentencheck.css` ein **`REDESIGN LAYER`** am
Dateiende hinzu (gewinnt per Quellreihenfolge). Vier Werte ließen sich nur an
ihrer Originalstelle korrigieren, dort mit `/* RD: */` markiert:
`.wizard-shell` (Padding 1.5→2.5rem oben, 5→6rem unten), `.card`
(`box-shadow` → `none`), `.wizard-progress` (4px/`--rc-neutral-high` →
3px/`--rc-neutral`), `#proto-bar` (Hintergrund → `--rc-surface-sunken`, damit die
Leiste als Dev-Streifen und nicht als Produkt-Chrome liest — Höhe absichtlich
unverändert, weil `.portal nav.side{top:3.125rem}` daran gemessen ist).

### Logo-Theming — die Entscheidung

**Ein einziges inline-SVG für beide Themes**, kein `[data-theme]`-Umschalten,
keine zwei DOM-Varianten:

- Die 11 Buchstabenpfade liegen in der Quelldatei in `<g fill="currentColor">`
  → sie folgen der `color` des Headers und sind damit automatisch theme-fähig
  (`#050505` hell / `#f8f8f8` dunkel, gemessen).
- Das „+" ist die **einzige** hart kodierte Farbe im Asset. `app.js` hängt genau
  diesem einen Pfad `class="sb-plus"` an, und die neue Regel
  `.sb-plus{fill:var(--rc-brand)}` färbt ihn (`#2196f3` hell / `#64b5f6` dunkel,
  gemessen). Deshalb wird `extraetf-logo-dark.svg` zur Laufzeit **nicht**
  gebraucht.
- Der Pfad-String ist in `app.js` einkompiliert (`SB_PLUS`, `SB_LETTERS`), nicht
  gefetcht: ein `<img>` kann `currentColor` nicht erben, und ein Fetch würde
  unter `file://` brechen.
- Derselbe `SB_PLUS`-String ist mit `viewBox="966.4 21.55 84 84"` exakt
  `extraetf-mark.svg` — die Kompaktmarke kostet also 0 zusätzliche Bytes.
  Damit rendert `.rc-head` in Sidebar/Topbar jetzt das echte Brand-„+"
  (`.rc-mark-plus`) statt des CSS-Häkchens im grünen Quadrat. Die alte
  `.rc-mark`-Regel bleibt als Fallback für handgeschriebene Lockups
  (`design-system.html`).
- Die beiden `.svg`-Dateien bleiben als extrahierte Quell-Assets liegen (und für
  `<img>`-Verwendung, wo `currentColor` nicht hinkommt).

### Neue Komponenten

| Klasse | Zweck |
|---|---|
| `.rc-header` + `.rc-header-logo` / `.rc-header-product` / `.rc-header-end` | White-Label-Produktheader, von `app.js` als erstes Element in `<body>` injiziert. Randlos auf der Seitenfläche, `min-height` 4.5rem (5rem ≥48rem), Wortmarke 28px (22px <35rem). Vorbild: der randlose Funnel-Header aus `avd.html` (`min-h-[96px]`), nicht der `border-b`-Header aus `register.html`. |
| `.rc-mark-plus` | echtes „+"-Glyph in `.rc-head` (überschreibt den `.rc-mark`-Platzhalter) |
| `details.disclose` + `.disclose-body` | **die** Progressive-Disclosure-Komponente (REDESIGN-Gesetz 2). Summary = leises Textlink-Affordance (`copy-sm`, `--rc-text-muted`, kein Kasten), Panel = weiche `--rc-neutral`-Fläche. Nutzt den Chevron des QA-`summary::after`-Blocks weiter (nur `margin-left:auto` zurückgenommen + verkleinert) → **kein doppelter Chevron, niemals ein ▸/⌄-Zeichen in ein Summary schreiben.** Reingelegte `.why-box`/`.find-hint`/`.note-box` verlieren im Panel automatisch Kasten und Icon. |
| `.warn-quiet` | leiser Inline-Hinweis in Warnfarbe, ohne Kasten — die Pflicht-Zulagen-Rückzahlungs-Warnung überall außer auf der echten Kündigen-Wahl (r52 Weg 4), wo `.warn-box` laut bleibt. Funktioniert allein und zusammen mit `.warn-box`. |
| `.wizard-q` | Frageblock: große ruhige Type (35px → 44px ≥48rem), max. Textbreite, Luft nach oben. `.wizard-shell>header` erbt dieselben Regeln, damit die bestehenden Screens ohne HTML-Änderung mitziehen. |
| `.card-quiet` | Karte ohne Rahmen/Schatten für die Screens, auf denen Weißraum strukturieren soll. Zusätzlich: `.card .card` verliert seinen Rahmen (keine Kästen in Kästen). |
| `.option-tile.is-picked` | Auto-Advance-Affordance: 250 ms „ja, das da" vor der Navigation, von `RC.autoAdvance()` gesetzt |

### `app.js`

`buildHeader()` (Opt-out: `<body data-rc-header="off">`), `wordmark()`,
`brandMark()`, `lockup()`, `RC.autoAdvance(tileEl, nextHref, writeFn)`.
`#proto-bar` bleibt unverändert in Funktion und ID (Smoke-Test), nur sein Label
ist jetzt „**Prototyp** · RentenCheck-Index" — der Produkt-Header führt zur
Journey (`r1-landing.html`), die Dev-Galerie hängt am Proto-Bar. Die mobile
`.portal-topbar` hat ihr (nun dreifaches) Brand-Lockup gegen den aktuellen
Seitentitel getauscht.
