diff --git a/README.md b/README.md
index 0ca7144..70a0012 100644
--- a/README.md
+++ b/README.md
@@ -43,6 +43,12 @@ angezeigt. Der Ablauf bleibt eine Simulation ohne Zahlung oder verbindliche
Beratung. Buchungen liegen lokal in `src/server/data/bookings.csv`. Diese Datei
nicht vorab leer anlegen: Das Backend erzeugt sie bei der ersten Buchung selbst.
+## Beraterkontakt testen
+
+Die [Kontakt-Testseite](http://localhost:5173/contact-test.html?berater-id=relindis-agethen)
+öffnet das neue Popup. Kundenprüfung, Mailpit-Versand, Profil-Einbindung und
+Checkliste sind in [docs/contact.md](docs/contact.md) dokumentiert.
+
## Prüfen
```bash
diff --git a/berater.html b/berater.html
new file mode 100644
index 0000000..722f850
--- /dev/null
+++ b/berater.html
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+ Beraterprofil · Tri-Hub
+
+
+
+
+
+
+
+
+
+
+
PERSÖNLICH FÜR DICH DA
+
Lerne deine Ernährungsberater kennen.
+
Hinter einer guten Ernährungsstrategie stehen Menschen, die zuhören und ihre Erfahrung in deinen Alltag und dein Training einbringen.
+ Testseite für das spätere Beraterprofil. Nutze eine
+ E-Mail-Adresse aus einer vorhandenen Testbuchung.
+
+
+
+
+
diff --git a/docs/contact-integration.md b/docs/contact-integration.md
new file mode 100644
index 0000000..8d04360
--- /dev/null
+++ b/docs/contact-integration.md
@@ -0,0 +1,103 @@
+# Übergabe an die KI der Beraterprofilseite
+
+Binde das vorhandene Kontakt-Popup in die Kontaktbuttons der Beraterprofile
+ein. Übergib ausschließlich die Berater-ID als `advisorId`. Implementiere
+kein eigenes Kontaktformular und keine separate E-Mail-Prüfung.
+
+## Relevante Dateien
+
+- `src/shared/berater-daten.json`: gemeinsame Datenquelle für Profile und Kontakt;
+ Array mit eindeutigen String-IDs, `vorname`, `nachname`, optionalem `spitzname` und `email`. Eigene öffentliche Profilfelder dürfen ergänzt werden.
+- `src/shared/berater.js`: `findAdvisor(advisorId)` liefert den passenden Eintrag.
+- `src/features/contact/contact-dialog.js`: exportiert `openContactDialog({ advisorId })`.
+- `src/features/contact/contact.css`: wird automatisch vom Popup-Modul importiert;
+ die Schriftdateien liegen unter `public/fonts/`.
+- `src/features/contact/contact-entry.js` und `contact-test.html`: Beispielintegration.
+- `src/server/services/contact-service.js`: prüft Kunden-E-Mail und Berater-ID,
+ lädt die Empfängeradresse serverseitig aus der JSON.
+- `src/server/swagger.json`: API-Vertrag für `POST /api/contact`.
+
+## Datenvertrag
+
+```json
+[
+ {
+ "id": "relindis-agethen",
+ "vorname": "Relindis",
+ "nachname": "Agethen",
+ "spitzname": "Lilli",
+ "email": "relindis.agethen@tri-hub.de"
+ }
+]
+```
+
+Die Profilseite und das Popup müssen dieselben IDs verwenden. E-Mail-Adressen
+werden explizit gepflegt und nicht aus Namen erzeugt. Sie müssen auf `@tri-hub.de`
+enden. Keine vertraulichen Informationen in die JSON schreiben: Sie ist Teil
+des Frontends. Nach Änderungen API neu starten und Frontend neu bauen.
+
+## Einzubindender Code
+
+Beispielbutton (ID aus dem jeweiligen Profil):
+
+```html
+
+```
+
+Im JavaScript-Modul der Profilseite, einmalig registrieren:
+
+```js
+// Importpfad relativ zu dieser Datei anpassen.
+import { openContactDialog } from "./src/features/contact/contact-dialog.js";
+
+document.addEventListener("click", (event) => {
+ const button = event.target.closest("button[data-berater-id]");
+ if (!button) return;
+ openContactDialog({ advisorId: button.dataset.beraterId });
+});
+```
+
+Bei dynamischer Erzeugung des Buttons die ID aus dem Profil setzen:
+
+```js
+button.dataset.beraterId = profile.id;
+```
+
+Alternativ direkt am einzelnen Button binden (nicht zusätzlich zur Delegation):
+
+```js
+button.addEventListener("click", () => {
+ openContactDialog({ advisorId: profile.id });
+});
+```
+
+Falls die Profilseite selbst Daten aus der gemeinsamen Datei benötigt:
+
+```js
+// Beispiel für ein Modul direkt unter src/; Pfad ggf. anpassen.
+import berater from "./shared/berater-daten.json" with { type: "json" };
+
+const profile = berater.find((entry) => entry.id === advisorId);
+// profile.vorname, profile.nachname, profile.spitzname und profile.email sind verfügbar.
+```
+
+Das Popup zeigt den getrimmten `spitzname`, falls nicht leer, sonst den
+getrimmten `vorname`. `nachname` und das bisherige Feld `name` werden für die
+Überschrift nicht verwendet. Es wird kein „kontaktieren“ angehängt.
+
+Das Popup ermittelt den Anzeigenamen selbst und sendet beim Absenden
+`{ email, advisorId, reason, simulationAccepted }` an `/api/contact`. Die
+Profilseite muss weder Name noch E-Mail an das Popup schicken. Kundenprüfung,
+Simulation, Mailpit-Versand und Microsoft-Bookings-Weiterleitung sind bereits
+implementiert. Unbekannte IDs sperren das Absenden.
+
+## Testen
+
+Mit laufendem Frontend, API und Mailpit:
+`http://localhost:5173/contact-test.html?berater-id=relindis-agethen`.
+Für die Kundenadresse eine vorhandene Testbuchung verwenden. Die Prüfung
+findet ausschließlich beim Absenden statt. Der Branch ist
+`feature/terminbuchung-kontaktfeld`; die Kontaktdateien müssen im Arbeitsstand
+der Profilseite vorhanden sein.
diff --git a/docs/contact.md b/docs/contact.md
new file mode 100644
index 0000000..dc85f84
--- /dev/null
+++ b/docs/contact.md
@@ -0,0 +1,154 @@
+# Beraterkontakt (Simulation)
+
+## Testseite starten
+
+Wie bei der Buchung in drei Terminals starten:
+
+```sh
+npm run dev:mail
+npm run dev:server
+npm run dev
+```
+
+Öffne .
+Der Button öffnet ein modales Fenster. Gib eine E-Mail aus einer vorhandenen
+Buchung in `src/server/data/bookings.csv` ein, wähle einen Kontaktgrund und
+bestätige den Simulationshinweis. Beim Klick auf „Anfrage senden & Termin wählen“
+prüft der Server die E-Mail und versendet nur bei vorhandener Buchung. Bei Bedarf zuerst
+über die bestehende Angebotsseite eine Testbuchung anlegen.
+
+Nach dem Absenden müssen zwei getrennte Nachrichten in
+ erscheinen: an die Kundenadresse und beispielsweise
+`relindis.agethen@tri-hub.de`. Der Browser wechselt anschließend zur vom Auftrag
+vorgegebenen Microsoft-Bookings-Adresse. Es wird dort kein Termin automatisch
+gebucht. Die Checkliste spricht von Microsoft Forms; umgesetzt ist der konkret
+angegebene Bookings-Link.
+
+## Checkliste
+
+- [x] Kontaktgrund auswählbar: Coaching, Ernährungsplan, Wettkampfverpflegung,
+ Regeneration, Paketfragen, Termin und Sonstiges.
+- [x] Getrennte Eingangsbestätigungen an Kunde und Berater, ausschließlich
+ simuliert über Mailpit, mit Kontaktgrund und Simulationshinweis.
+- [x] Weiterleitung zur vorhandenen Microsoft-Bookings-Adresse nach Annahme
+ beider Nachrichten durch Mailpit; zusätzlicher Link als Rückfalloption.
+- [x] Kontakt nur nach erfolgreichem E-Mail-Abgleich mit `bookings.csv`;
+ der Server prüft beim tatsächlichen Absenden erneut.
+
+## Einbindung in Beraterprofile
+
+Die Profilseite importiert `openContactDialog` aus
+`src/features/contact/contact-dialog.js` und übergibt ausschließlich die ID:
+
+```js
+import { openContactDialog } from "./src/features/contact/contact-dialog.js";
+
+button.addEventListener("click", () => {
+ openContactDialog({ advisorId: profile.id });
+});
+```
+
+Den Importpfad relativ zur einbindenden Datei anpassen. Das Modul bringt die
+Popup-Styles und die lokalen DM-Sans-Schriftdateien mit. Die vollständige
+Einbauanleitung für die Profilseite steht in [contact-integration.md](contact-integration.md).
+
+Die gemeinsame Datenquelle `src/shared/berater-daten.json` enthält ein Array
+mit eindeutigen String-IDs und den Feldern `id`, `vorname`, `nachname`, `spitzname`, `email`. Die vorhandenen Einträge enthalten die Beraterprofile. Zusätzliche Profilfelder sind möglich. Die Datei
+wird im Frontend eingebunden: ausschließlich öffentliche Profildaten eintragen.
+
+`src/shared/berater.js` stellt `findAdvisor(advisorId)` bereit. Popup und Server
+verwenden dieselbe Zuordnung. Der Server holt die Empfängeradresse ausschließlich
+aus dieser JSON; vom Browser gesendete Namen oder Empfängeradressen werden
+nicht übernommen. E-Mail-Adressen müssen gültig sein und auf `@tri-hub.de`
+enden. Eine unbekannte ID wird abgelehnt und sperrt das Absenden im Popup.
+
+Die Testseite liest den URL-Parameter `berater-id`, zum Beispiel
+`contact-test.html?berater-id=maren-hoffmann`. Ohne Parameter verwendet sie
+`relindis-agethen`. Nach Änderungen an der JSON den API-Server neu starten und für
+das Deployment das Frontend neu bauen.
+
+Das native `dialog` hält den Tastaturfokus im Popup. Escape und Schließen
+bringen ihn zum auslösenden Button zurück. Während einer Anfrage ist Schließen
+kurz gesperrt, damit der Versand nicht unbemerkt weiterläuft. Das Fenster ist
+auf schmalen Bildschirmen scrollbar. Die E-Mail wird bei jedem Absenden geprüft;
+ein separater Prüfbutton ist nicht erforderlich. Es gibt keine zusätzlichen
+Laufzeitabhängigkeiten.
+
+## API
+
+Der vollständige OpenAPI-Vertrag steht in `src/server/swagger.json`.
+Beide Endpunkte erwarten POST mit `Content-Type: application/json`, maximal
+8 KiB. Fehler enthalten `error.code` und `error.message`.
+
+| Endpunkt | Eingabe | Erfolg |
+| ----------------------- | --------------------------------------------------- | ---------------------------------------------------------- |
+| `/api/contact/validate` | `{ "email": "kunde@example.test" }` | 200: `{ "valid": true, "email": "kunde@example.test" }` |
+| `/api/contact` | E-Mail, Berater-ID, Grund, Zustimmung (siehe unten) | 201: `simulated`, `emailStatus: "accepted"`, `redirectUrl` |
+
+```json
+{
+ "email": "kunde@example.test",
+ "advisorId": "relindis-agethen",
+ "reason": "coaching",
+ "simulationAccepted": true
+}
+```
+
+400 bedeutet ungültige Eingaben, 403 keine passende Buchung, 405 falsche Methode,
+413 zu große Anfrage, 415 falscher Medientyp, 503 nicht lesbarer CSV-Speicher oder ungültige Beraterkontaktdaten.
+502 bedeutet, dass mindestens eine Mailannahme fehlgeschlagen oder unklar ist;
+der Browser leitet dann nicht weiter. 500 bezeichnet einen unerwarteten Fehler.
+Die CSV wird ausschließlich gelesen und bleibt unverändert. Der separate
+Validierungsendpunkt bleibt für API-Nutzer verfügbar; das Popup verwendet
+ausschließlich `/api/contact` mit integrierter Kundenprüfung.
+
+## Grenzen der Simulation
+
+Der E-Mail-Abgleich ignoriert Groß-/Kleinschreibung und äußere Leerzeichen.
+Er bestätigt einen Eintrag in der Buchungsdatei, nicht den Besitz des Postfachs.
+Die Prüfantwort ist kein Anmeldetoken und gibt keine Buchungsdetails zurück.
+Vor einem öffentlichen Produktiveinsatz wären eine Anmeldung oder Bestätigung
+per E-Mail sowie ein Schutz vor automatisierten Adressabfragen erforderlich.
+
+Kontaktmails verwenden fest `127.0.0.1:1025`, unabhängig von einer eventuell
+externen SMTP-Konfiguration des Buchungsablaufs. Mailpit muss deshalb auf
+demselben Host laufen. Kunden- und Berateradresse sind simulierte Empfänger;
+es erfolgt kein externer Versand. SMTP-Annahme ist keine reale Zustellbestätigung.
+
+Kontaktanfragen werden nicht dauerhaft gespeichert und haben keinen
+Idempotenzschlüssel. Doppelklicks sind während des Versands gesperrt. Bei
+Verbindungsabbrüchen oder Teilfehlern kann bereits eine Nachricht vorliegen:
+vor manuellem Wiederholen Mailpit prüfen. Es gibt keinen automatischen Neuversand.
+
+## Prüfungen
+
+- `npm test`: API, CSV-Abgleich, erneute Validierung beim Absenden, ungültige
+ Eingaben, SMTP-Empfänger und Teilfehler; isolierte temporäre CSV-Dateien.
+- `npm run test:browser`: vollständiger Ablauf einschließlich abgefangener
+ externer Weiterleitung, unbekannter Adresse, geänderter E-Mail, Versandfehler,
+ mobiler Darstellung, Fokus und Escape; zusätzlich bestehende Buchungstests.
+- `npm run build` und `git diff --check`.
+- Lokaler SMTP-Smoke-Test mit Mailpit: beide Empfänger und Simulationshinweis
+ in den empfangenen Nachrichten geprüft.
+
+Chromium muss für Playwright installiert sein (`npx playwright install chromium`).
+In dieser Arbeitsumgebung liegt der Testbrowser unter `/tmp/trihub-playwright`;
+hier mit `PLAYWRIGHT_BROWSERS_PATH=/tmp/trihub-playwright npm run test:browser` starten.
+Der globale Formatcheck hat bereits bestehende Abweichungen in `src/main.js`
+und `src/components/navigationsbar/README.md`; die Kontaktdateien sind formatiert.
+Der bestehende Gesamt-Build meldet außerdem fehlende ältere Schriftdateien aus
+dem globalen Stylesheet. Das Kontakt-Popup verwendet eigene lokale Schriftdateien.
+
+### Ergebnis dieser Umsetzung
+
+31 API-/Servicetests und alle vier Kontakt-Browsertests bestanden.
+Build, lokale Mailpit-Prüfung und Formatierung der geänderten Dateien bestanden.
+Die bestehenden Buchungs-Browsertests sind teilweise nicht mehr an das schon
+vorhandene Buchungs-Popup angepasst: Sie suchen Formularfelder, ohne vorher
+„Jetzt buchen“ zu klicken. Weitere Bestandsfälle erwarten einen nicht mehr vorhandenen Startseiten-Link
+oder eine Seitennavigation, wo bereits ein Popup geöffnet wird. Beim ursprünglichen Gesamtlauf bestanden 7 von 15 Browsertests; die 8 Fehler
+betrafen ausschließlich die unveränderten Buchungstests. Die gezielten
+Kontakttests werden nach Anpassungen separat ausgeführt.
+`git diff --check` ist für die eigenen Änderungen sauber; die zuvor bereits
+geänderte `bookings.csv` erzeugt separat CRLF-Whitespace-Hinweise und wurde
+von dieser Umsetzung nicht verändert.
diff --git a/public/fonts/DM-Sans-LICENSE.txt b/public/fonts/DM-Sans-LICENSE.txt
new file mode 100644
index 0000000..917aaa6
--- /dev/null
+++ b/public/fonts/DM-Sans-LICENSE.txt
@@ -0,0 +1,93 @@
+Copyright 2014 The DM Sans Project Authors (https://github.com/googlefonts/dm-fonts) DMSans-Italic[opsz,wght].ttf: Copyright 2014 The DM Sans Project Authors (https://github.com/googlefonts/dm-fonts)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/public/fonts/dm-sans-latin-400-normal.woff2 b/public/fonts/dm-sans-latin-400-normal.woff2
new file mode 100644
index 0000000..0b8bc55
Binary files /dev/null and b/public/fonts/dm-sans-latin-400-normal.woff2 differ
diff --git a/public/fonts/dm-sans-latin-500-normal.woff2 b/public/fonts/dm-sans-latin-500-normal.woff2
new file mode 100644
index 0000000..48e1612
Binary files /dev/null and b/public/fonts/dm-sans-latin-500-normal.woff2 differ
diff --git a/public/fonts/dm-sans-latin-700-normal.woff2 b/public/fonts/dm-sans-latin-700-normal.woff2
new file mode 100644
index 0000000..26edc56
Binary files /dev/null and b/public/fonts/dm-sans-latin-700-normal.woff2 differ
diff --git a/src/components/navigationsbar/navigation.html b/src/components/navigationsbar/navigation.html
index 37097df..1d4044c 100644
--- a/src/components/navigationsbar/navigation.html
+++ b/src/components/navigationsbar/navigation.html
@@ -1,7 +1,7 @@