Zum Inhalt
← Sektionen & Funktionen

Personen-Detailseite

Eine Seite je Person: Name, Rolle und ein Satz Zuständigkeit, Sprachen mit lang, Terminweg genau bei ihr mit Sprechtagen und freien Zeiten, Schwerpunkte in Alltagssprache, Werdegang, Vertretung bei Abwesenheit und ProfilePage-JSON-LD aus demselben Datensatz.

Praxis, Therapie, Kanzlei, Beratung: Rolle mit Fachgebiet, Sprechtage, Kasse, Schwerpunkte mit Fachwort, Notfallhinweis.

Eine eigene Seite für eine Person aus dem Team: Ärztin, Therapeut, Stylistin, Anwältin, Trainer. Oben stehen Name, Rolle und ein Satz, wofür man zu ihr kommt. Daneben der Terminweg genau bei dieser Person: nächste freie Zeiten oder ihre Sprechtage, Telefon, Buchung. Darunter Schwerpunkte in Alltagssprache, Sprachen, Ausbildung und Werdegang als kurze Liste und eine persönliche Notiz. Die Seite gibt strukturierte Daten als ProfilePage mit Person aus, aus demselben Datensatz wie der sichtbare Text.

Viele Besucher wählen bei Praxis, Salon oder Kanzlei nicht den Betrieb, sondern die Person. Sie suchen nach Name, Sprache oder Schwerpunkt und wollen genau bei ihr einen Termin. Eine Übersichtsseite mit Karten beantwortet das nicht. Die eigene Seite hat eine Adresse, die man teilen und finden kann, und führt ohne Umweg in die Buchung bei dieser Person.

Wann einsetzen

Praxis, Therapie, Friseur, Kosmetik, Kanzlei und Beratung, Kurs und Coaching, Fitness mit Trainer:innen, Immobilien mit Ansprechpersonen. Immer dann, wenn Kund:innen eine bestimmte Person buchen oder ansprechen.

Wann nicht

Nicht bei Betrieben, in denen die Person nicht wählbar ist (Pflegedienst-Touren, Hotline). Keine KI-Porträts und keine Stockfotos statt echter Fotos: bis zum Shooting ein markierter Platzhalter. Keine Heilversprechen, keine erfundenen Bewertungen oder Siegel. Private Angaben nur mit Einwilligung der Person.

Varianten

  • Ärztin oder Therapeut: Schwerpunkte in Alltagssprache mit Fachwort, Sprechtage, Sprachen, Ausbildung, Kassen und Selbstzahler
  • Stylistin: Spezialgebiet, Stufe mit Preis, Arbeitsproben, Buchen direkt bei ihr
  • Kompakt als Seitenleiste mit Terminweg für Kanzlei und Beratung
  • Person nicht verfügbar (Elternzeit, ausgeschieden): Hinweis mit Vertretung statt toter Seite

Umsetzung

Ein Datensatz je Person (name, honorificPrefix, jobTitle, Schwerpunkte, knowsLanguage, alumniOf, worksFor, Sprechtage, Buchungs-URL, Foto, Status) erzeugt sichtbaren Text und JSON-LD. JSON-LD: ProfilePage mit mainEntity Person (name, jobTitle, knowsLanguage, worksFor als Organisation des Betriebs mit Adresse, image, url, sameAs nur für echte Profile). Brotkrumen Startseite › Team › Name mit BreadcrumbList. Kopf: h1 mit Name, darunter Rolle, ein Satz Zuständigkeit. Terminweg als eigene Spalte: nächste freie Zeiten aus der Buchungs-Schnittstelle oder Sprechtage als Wochenleiste mit heute markiert; Buchung über termin-buchung-zwei-klick oder Telefon. Schwerpunkte als Liste mit Alltagswort und Fachwort. Sprachen mit lang-Attribut und Sprachname in der eigenen Sprache. Werdegang als dl mit Jahren in Tabellenziffern. Status „nicht verfügbar“ entfernt den Terminweg und verweist auf die Vertretung. Unten Weiter zur nächsten Person und zurück zum Team.

Robust umsetzen

Telefon. Name, Rolle und Terminweg zuerst, dann Porträt im Format 4:5 in halber Breite neben dem Namen. Terminzeiten als waagerechte Reihe von Knöpfen mit Umbruch. Eine feste Leiste unten mit „Termin bei …“ und Anrufen.

Ohne Bewegung. Porträt und Zustände erscheinen ohne Überblendung.

Barrierefrei. Eine h1 mit dem Namen. Rolle nicht nur als Bildunterschrift. Terminzeiten als Knöpfe mit vollem Namen („Dienstag, 10. November, 9:20 Uhr“). Sprachen mit lang-Attribut, damit Screenreader richtig aussprechen. Porträt mit sinnvollem alt; der Platzhalter ist als Platzhalter benannt. Status „nicht verfügbar“ steht als Text, nicht nur als Farbe.

Code

<!DOCTYPE html>
<!--
  Personen-Detailseite
  Fundus · Sektionen & Funktionen · https://cecodesign.de/lab/fundus/sektionen/person-detailseite/
  Eigenständige Datei. Bilder liegen hier unter bilder/ – eigene einsetzen.
-->
<html lang="de" data-v="aerztin">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Personen-Detailseite · Fundus-Demo</title>
<script>document.documentElement.classList.add('js');if(matchMedia('(prefers-reduced-motion: reduce)').matches)document.documentElement.classList.add('reduziert')</script>
<script type="application/ld+json" id="ld">{}</script>
<style>
@font-face { font-family: 'Hedvig Letters Serif'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/hedvig-letters-serif:vf@latest/latin-opsz-normal.woff2') format('woff2-variations'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'Google Sans Flex'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/google-sans-flex:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 100 1000; font-display: swap; }
@font-face { font-family: 'Kalnia'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/kalnia:vf@latest/latin-full-normal.woff2') format('woff2-variations'); font-weight: 100 700; font-stretch: 100% 125%; font-display: swap; }
@font-face { font-family: 'Familjen Grotesk'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/familjen-grotesk:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 400 700; font-display: swap; }
@font-face { font-family: 'Mrs Saint Delafield'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/mrs-saint-delafield@latest/latin-400-normal.woff2') format('woff'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'Geist Mono'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/geist-mono:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-display: swap; }

/* Gerüst fest, Token aus dem Rezept. Ärztin: praxis-gesundheit-ruhig */
:root {
  --grund: #f5f7f7; --flaeche: #e2e0de; --karte: #ffffff; --text: #363b3b; --leise: #505a5a; --linie: #dcdfdd;
  --marke: #005450; --marke-tief: #003f3c; --marke-text: #ffffff; --marke-hell: #e3eeec; --akzent: #ffc776; --akzent-schrift: #7a4f00;
  --kurve: cubic-bezier(0.2, 0, 0.38, 0.9); --zu: cubic-bezier(0.2, 0, 1, 0.9); --bild: cubic-bezier(0, 0, 0.3, 1);
  --ui: 150ms; --wechsel: 250ms; --wechsel-zu: 170ms; --bild-dauer: 700ms;
  --h: 'Hedvig Letters Serif', Georgia, serif; --h-gewicht: 400; --h-breite: 100%; --h-lauf: -0.01em;
  --t: 'Google Sans Flex', system-ui, sans-serif; --r: 16px; --r-klein: 12px; --r-knopf: 999px;
}
/* Stylistin: friseur-salon-handschrift */
:root[data-v="stylistin"] {
  --grund: #f4e9e4; --flaeche: #e9dbd4; --karte: #fbf5f2; --text: #1e1412; --leise: #6e5a55; --linie: #d9c8c0;
  --marke: #9b1d2e; --marke-tief: #7c1624; --marke-text: #ffffff; --marke-hell: #efdcd6; --akzent: #9b1d2e; --akzent-schrift: #9b1d2e;
  --kurve: cubic-bezier(0.625, 0.05, 0, 1); --zu: cubic-bezier(0.625, 0.05, 0, 1); --bild: cubic-bezier(0.625, 0.05, 0, 1);
  --ui: 250ms; --wechsel: 400ms; --wechsel-zu: 260ms; --bild-dauer: 725ms;
  --h: 'Kalnia', Georgia, serif; --h-gewicht: 500; --h-breite: 112%; --h-lauf: -0.02em;
  --t: 'Familjen Grotesk', system-ui, sans-serif; --r: 16px; --r-klein: 8px; --r-knopf: 999px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html { background: var(--grund); color: var(--text); font-family: var(--t); -webkit-font-smoothing: antialiased; }
:root:has(:focus-visible) { scroll-behavior: auto; }
body { font-size: 17px; line-height: 1.6; }
a { color: inherit; text-underline-offset: 3px; text-decoration-thickness: 1px; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
:focus-visible { outline: 2px solid var(--marke); outline-offset: 3px; }
.vh { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
.vh:focus { position: fixed; left: 12px; top: 12px; width: auto; height: auto; clip-path: none; white-space: normal; z-index: 100; padding: 10px 14px; background: #fff; color: #111; outline: 3px solid #111; }
.ziffern { font-variant-numeric: tabular-nums lining-nums; }
h1, h2, h3 { font-family: var(--h); font-weight: var(--h-gewicht); font-stretch: var(--h-breite); letter-spacing: var(--h-lauf); line-height: 1.1; text-wrap: balance; font-optical-sizing: auto; }
p { text-wrap: pretty; }
.huelle { max-width: 1200px; margin: 0 auto; padding: 0 clamp(18px, 3.4vw, 44px); }
.label { font-size: 14.5px; font-weight: 600; color: var(--leise); }
:root[data-v="stylistin"] .label { font-size: 13px; text-transform: uppercase; letter-spacing: .06em; }

/* Kopf */
.kopf { border-bottom: 1px solid var(--linie); }
.kopf .huelle { display: flex; align-items: center; gap: 22px; min-height: 68px; }
.marke { display: flex; align-items: center; gap: 11px; text-decoration: none; margin-right: auto; }
.marke svg { width: 32px; height: 32px; flex: none; }
.marke b { display: block; font: var(--h-gewicht) 20px/1.05 var(--h); font-stretch: var(--h-breite); }
.marke small { display: block; font-size: 13px; color: var(--leise); }
.kopf nav ul { display: flex; gap: 20px; list-style: none; }
.kopf nav a { text-decoration: none; font-size: 15.5px; color: var(--leise); padding: 6px 0; border-bottom: 2px solid transparent; transition: color var(--ui) var(--kurve), border-color var(--ui) var(--kurve); }
.kopf nav a:hover, .kopf nav a[aria-current] { color: var(--text); border-color: var(--marke); }
.pille { display: inline-flex; align-items: center; gap: 8px; min-height: 44px; padding: 0 18px; border-radius: var(--r-knopf); background: var(--marke); color: var(--marke-text); font-weight: 600; font-size: 15.5px; text-decoration: none; white-space: nowrap; transition: background-color var(--ui) var(--kurve); }
.pille:hover { background: var(--marke-tief); }
.tel-kopf { font-weight: 600; text-decoration: none; white-space: nowrap; font-size: 15.5px; }

.krumen { padding: 18px 0 0; font-size: 14.5px; color: var(--leise); }
.krumen ol { display: flex; flex-wrap: wrap; list-style: none; }
.krumen li + li::before { content: '›'; margin: 0 9px; }
.krumen [aria-current] { color: var(--text); }

/* Einstieg */
.einstieg { display: grid; grid-template-columns: minmax(0, 300px) minmax(0, 1fr) minmax(0, 380px); gap: clamp(22px, 3.4vw, 52px); padding: clamp(24px, 3.4vw, 48px) 0 clamp(36px, 4.6vw, 64px); align-items: start; }
.portraet { position: relative; aspect-ratio: 4 / 5; border-radius: var(--r); overflow: hidden; background: var(--marke-hell); }
.portraet svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.portraet .ph { position: absolute; left: 12px; bottom: 12px; right: 12px; display: flex; justify-content: space-between; gap: 8px; font: 500 10px/1.4 'Geist Mono', monospace; letter-spacing: .12em; text-transform: uppercase; color: var(--text); background: rgba(255, 255, 255, .8); padding: 6px 8px; border-radius: 6px; }
.portraet.blendet { animation: bild var(--bild-dauer) var(--bild) both; }
@keyframes bild { from { opacity: 0; transform: translateY(8px); } }
.unterschrift { display: none; font: 400 44px/1 'Mrs Saint Delafield', cursive; color: var(--marke); margin: 6px 0 0 10px; }
:root[data-v="stylistin"] .unterschrift { display: block; }
.rolle { display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: center; }
.rolle .stufe { display: inline-flex; padding: 3px 10px; border-radius: var(--r-knopf); background: var(--marke-hell); color: var(--marke); font-size: 13.5px; font-weight: 700; }
:root[data-v="stylistin"] .rolle .stufe { color: var(--marke-tief); }
h1 { font-size: min(clamp(2.3rem, 1.3rem + 3vw, 3.9rem), 15cqi); margin-top: 12px; }
.kopftext { container-type: inline-size; min-width: 0; }
:root[data-v="stylistin"] h1 { font-size: min(clamp(2.5rem, 1.3rem + 3.6vw, 4.4rem), 13cqi); line-height: .98; }
h1:focus { outline: none; }
.zustaendig { font-size: clamp(18px, 1rem + .35vw, 20.5px); line-height: 1.55; margin-top: 16px; max-width: 34em; }
.fakten { display: grid; gap: 0; margin-top: 24px; border-top: 1px solid var(--linie); }
.fakten div { display: grid; grid-template-columns: 8.5em minmax(0, 1fr); gap: 14px; padding: 11px 0; border-bottom: 1px solid var(--linie); }
.fakten dt { color: var(--leise); font-size: 15px; padding-top: 1px; }
.fakten dd { font-weight: 500; }
.sprachen { display: flex; flex-wrap: wrap; gap: 6px; list-style: none; }
.sprachen li { padding: 2px 10px; border: 1px solid var(--linie); border-radius: var(--r-knopf); font-size: 15px; background: var(--karte); }
.sprachen li small { color: var(--leise); font-size: 13px; margin-left: 4px; }

/* Terminweg */
.termin { background: var(--karte); border: 1px solid var(--linie); border-radius: var(--r); padding: clamp(18px, 2vw, 24px); position: sticky; top: 18px; }
.termin h2 { font-size: 1.5rem; }
.termin .unter { font-size: 15px; color: var(--leise); margin-top: 4px; }
.leistung { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 14px; }
.leistung button { padding: 7px 12px; border-radius: var(--r-knopf); border: 1px solid var(--linie); font-size: 14.5px; transition: background-color var(--ui) var(--kurve), border-color var(--ui) var(--kurve), color var(--ui) var(--kurve); }
.leistung button[aria-pressed="true"] { background: var(--text); color: var(--grund); border-color: var(--text); }
.tage { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 4px; margin-top: 16px; }
.tage div { text-align: center; padding: 7px 0 8px; border-radius: var(--r-klein); background: var(--grund); font-size: 13px; line-height: 1.3; color: var(--leise); }
.tage div b { display: block; font-size: 14px; color: var(--text); }
.tage div.frei { background: transparent; border: 1px dashed var(--linie); }
.tage div.heute { outline: 2px solid var(--marke); outline-offset: -2px; }
.zeiten-titel { display: flex; justify-content: space-between; align-items: baseline; margin-top: 18px; font-size: 15px; font-weight: 600; }
.zeiten-titel a { font-weight: 500; font-size: 14.5px; }
.zeiten { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin-top: 10px; }
.zeit { display: grid; gap: 0; text-align: left; padding: 9px 12px; border: 1.5px solid var(--linie); border-radius: var(--r-klein); font-size: 14px; color: var(--leise); line-height: 1.3; transition: border-color var(--ui) var(--kurve), background-color var(--ui) var(--kurve), color var(--ui) var(--kurve); }
.zeit b { font-size: 16.5px; color: var(--text); font-weight: 600; }
.zeit:hover { border-color: var(--marke); }
.zeit[aria-pressed="true"] { background: var(--marke); border-color: var(--marke); color: rgba(255, 255, 255, .86); }
.zeit[aria-pressed="true"] b { color: #fff; }
.auswahl { margin-top: 14px; display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--wechsel-zu) var(--zu); }
.auswahl.offen { grid-template-rows: 1fr; transition: grid-template-rows var(--wechsel) var(--kurve); }
.auswahl > div { overflow: hidden; }
.auswahl p { font-size: 15px; padding: 12px 14px; border-radius: var(--r-klein); background: var(--marke-hell); }
.buchen { display: flex; width: 100%; justify-content: center; align-items: center; gap: 8px; min-height: 52px; margin-top: 12px; border-radius: var(--r-knopf); background: var(--marke); color: var(--marke-text); font-weight: 700; font-size: 16px; transition: background-color var(--ui) var(--kurve), opacity var(--ui) var(--kurve); }
.buchen:hover:not([aria-disabled="true"]) { background: var(--marke-tief); }
.buchen[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }
.buchen.druck { background: var(--marke-tief); }
.zweiklick { font-size: 14px; color: var(--leise); margin-top: 10px; }
.zweiklick[hidden] { display: none; }
.oder { margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--linie); font-size: 15px; display: grid; gap: 4px; }
.oder a { font-weight: 700; font-size: 17px; text-decoration: none; }
.oder small { color: var(--leise); font-size: 14px; }
.abwesend { display: none; }
:root[data-abwesend] .verfuegbar { display: none; }
:root[data-abwesend] .abwesend { display: block; }
.abwesend .hinweis { margin-top: 12px; padding: 14px; border-radius: var(--r-klein); background: #fff4e2; border-left: 4px solid var(--akzent); font-size: 15.5px; }
:root[data-v="stylistin"] .abwesend .hinweis { background: var(--marke-hell); }
.vertretung { display: grid; grid-template-columns: 52px minmax(0, 1fr); gap: 12px; align-items: center; margin-top: 14px; padding: 12px; border: 1px solid var(--linie); border-radius: var(--r-klein); text-decoration: none; transition: border-color var(--ui) var(--kurve); }
.vertretung:hover { border-color: var(--marke); }
.mini { width: 52px; aspect-ratio: 1; border-radius: var(--r-klein); display: grid; place-items: center; background: var(--marke-hell); color: var(--marke); font: var(--h-gewicht) 19px/1 var(--h); font-stretch: var(--h-breite); }
.vertretung b { display: block; }
.vertretung span { font-size: 14px; color: var(--leise); }

/* Inhalt */
.inhalt { display: grid; grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr); gap: clamp(28px, 4vw, 72px); padding-block: clamp(40px, 5vw, 72px); border-top: 1px solid var(--linie); }
.inhalt h2 { font-size: clamp(1.75rem, 1.3rem + 1.6vw, 2.5rem); margin-bottom: 18px; }
.schwerpunkte { list-style: none; display: grid; }
.schwerpunkte li { display: grid; grid-template-columns: 34px minmax(0, 1fr); gap: 14px; padding: 16px 0; border-top: 1px solid var(--linie); }
.schwerpunkte li:last-child { border-bottom: 1px solid var(--linie); }
.schwerpunkte .nr { font: var(--h-gewicht) 22px/1.1 var(--h); color: var(--marke); }
.schwerpunkte b { font-size: 18.5px; font-weight: 600; display: block; }
.schwerpunkte .fach { color: var(--leise); font-size: 15px; }
.schwerpunkte p { margin-top: 4px; max-width: 52ch; }
.werdegang { display: grid; }
.werdegang div { display: grid; grid-template-columns: 6.5em minmax(0, 1fr); gap: 14px; padding: 11px 0; border-top: 1px solid var(--linie); font-size: 16px; }
.werdegang dt { color: var(--leise); }
.notiz { margin-top: 30px; padding: 22px 24px; border-radius: var(--r); background: var(--flaeche); }
.notiz blockquote { font: var(--h-gewicht) clamp(1.3rem, 1.1rem + .7vw, 1.65rem)/1.3 var(--h); font-stretch: var(--h-breite); }
.notiz figcaption { margin-top: 10px; font-size: 14.5px; color: var(--leise); }
.proben { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin-top: 14px; }
.probe { aspect-ratio: 4 / 5; border-radius: var(--r-klein); position: relative; overflow: hidden; }
.probe span { position: absolute; left: 8px; right: 8px; bottom: 8px; font: 500 9.5px/1.3 'Geist Mono', monospace; letter-spacing: .1em; text-transform: uppercase; background: rgba(251, 245, 242, .86); padding: 5px 6px; border-radius: 4px; color: var(--text); }
.nur-stylistin { display: none; }
:root[data-v="stylistin"] .nur-stylistin { display: block; }

/* Team */
.team { padding-block: clamp(34px, 4vw, 56px) clamp(48px, 6vw, 88px); border-top: 1px solid var(--linie); }
.team-kopf { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }
.team h2 { font-size: clamp(1.5rem, 1.2rem + 1vw, 2rem); }
.team ul { list-style: none; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
.team a.person { display: grid; grid-template-columns: 52px minmax(0, 1fr); gap: 12px; align-items: center; padding: 12px; border: 1px solid var(--linie); border-radius: var(--r-klein); text-decoration: none; background: var(--karte); transition: border-color var(--ui) var(--kurve); }
.team a.person:hover { border-color: var(--marke); }
.team a.person b { display: block; }
.team a.person span { font-size: 14px; color: var(--leise); }

/* Strukturierte Daten */
.daten-panel { display: none; padding-bottom: 48px; }
:root[data-ld] .daten-panel { display: block; }
.daten-panel pre { padding: 18px 20px; border-radius: var(--r-klein); background: #1d2222; color: #dfe6e4; font: 400 12.5px/1.6 'Geist Mono', monospace; overflow-x: auto; }
.daten-panel p { font-size: 14.5px; color: var(--leise); margin-bottom: 10px; }

.fuss { background: var(--marke); color: rgba(255, 255, 255, .86); font-size: 14.5px; padding: 36px 0 44px; }
:root[data-v="stylistin"] .fuss { background: var(--text); }
.fuss .huelle { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(190px, 100%), 1fr)); gap: 20px 36px; }
.fuss b { color: #fff; display: block; margin-bottom: 6px; }
.fuss a { color: #fff; }
.fuss ul { list-style: none; display: grid; gap: 4px; }
.fuss :focus-visible { outline-color: #fff; }

.leiste { display: none; }

/* Pult */
.pult { position: fixed; right: 14px; bottom: 14px; z-index: 40; width: 254px; padding: 12px; display: grid; gap: 9px; background: rgba(20, 22, 22, .94); color: #f3f3f0; border-radius: 10px; box-shadow: 0 12px 34px rgba(0, 0, 0, .24); }
.pult .mono { font: 500 10.5px/1.4 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; color: rgba(243, 243, 240, .72); }
.pult .reihe { display: grid; grid-template-columns: 1fr 1fr; border: 1px solid rgba(243, 243, 240, .24); border-radius: 6px; overflow: hidden; }
.pult button { padding: 8px 6px; font: 500 11.5px/1.2 'Geist Mono', monospace; color: rgba(243, 243, 240, .84); transition: background-color 150ms var(--kurve), color 150ms var(--kurve); }
.pult .reihe button + button { border-left: 1px solid rgba(243, 243, 240, .24); }
.pult button[aria-pressed="true"] { background: #f3f3f0; color: #141616; }
.pult .einzeln { border: 1px solid rgba(243, 243, 240, .24); border-radius: 6px; text-align: left; padding: 8px 10px; display: flex; justify-content: space-between; }
.pult :focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.pult .werte { font: 400 10.5px/1.5 'Geist Mono', monospace; color: rgba(243, 243, 240, .72); }

@media (max-width: 1080px) {
  .kopf nav { display: none; }
  .einstieg { grid-template-columns: minmax(0, 240px) minmax(0, 1fr); }
  .termin { grid-column: 1 / -1; position: static; }
}
@media (max-width: 760px) {
  .tel-kopf { display: none; }
  .einstieg { grid-template-columns: minmax(0, 1fr); }
  .einstieg .portraet-huelle { display: grid; grid-template-columns: minmax(0, 150px) minmax(0, 1fr); gap: 16px; align-items: end; }
  .inhalt { grid-template-columns: minmax(0, 1fr); }
  .team ul { grid-template-columns: minmax(0, 1fr); }
  .fakten div, .werdegang div { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  .leiste { display: grid; grid-template-columns: auto 1fr; gap: 8px; position: fixed; left: 0; right: 0; bottom: 0; z-index: 25; padding: 10px 12px calc(10px + env(safe-area-inset-bottom)); background: var(--karte); border-top: 1px solid var(--linie); }
  .pult { bottom: calc(82px + env(safe-area-inset-bottom)); } /* über der Leiste, sonst verdeckt „Steuerung +“ den Hauptknopf */
  .leiste a { display: flex; align-items: center; justify-content: center; gap: 8px; min-height: 48px; padding: 0 16px; border-radius: var(--r-knopf); font-weight: 700; text-decoration: none; border: 1.5px solid var(--text); }
  .leiste a.haupt { background: var(--marke); color: var(--marke-text); border-color: var(--marke); }
  .fuss { padding-bottom: 100px; }
}
@media (max-width: 380px) { .zeiten { grid-template-columns: minmax(0, 1fr); } .tage div { font-size: 12px; } }
.vorschau body { overflow: hidden; }
.vorschau .leiste { display: none; }

.reduziert *, .reduziert *::before, .reduziert *::after { animation: none !important; transition-duration: .01ms !important; }
@media (forced-colors: active) {
  button, [role=button], [role=tab], [role=switch], input, select, textarea, .pille, .zeit { border: 1px solid ButtonText !important; }
  .zeit[aria-pressed="true"] { outline: 3px solid Highlight; }
}
</style>
</head>
<body>
<a class="vh" href="#titel">Zum Inhalt springen</a>
<header class="kopf">
  <div class="huelle">
    <a class="marke" href="#" id="marke"></a>
    <nav aria-label="Hauptmenü"><ul id="nav"></ul></nav>
    <a class="tel-kopf ziffern" id="tel-kopf" href="#"></a>
    <a class="pille" href="#termin" id="kopf-handlung"></a>
  </div>
</header>

<main>
  <div class="huelle">
    <nav class="krumen" aria-label="Brotkrumen"><ol><li><a href="#">Startseite</a></li><li><a href="#" id="krume-team"></a></li><li><span aria-current="page" id="krume"></span></li></ol></nav>
    <div class="einstieg">
      <div class="portraet-huelle">
        <figure class="portraet" id="portraet"></figure>
        <p class="unterschrift" id="unterschrift" aria-hidden="true"></p>
      </div>
      <div class="kopftext">
        <p class="rolle"><span class="label" id="rolle"></span><span class="stufe" id="stufe"></span></p>
        <h1 id="titel" tabindex="-1"></h1>
        <p class="zustaendig" id="zustaendig"></p>
        <dl class="fakten" id="fakten"></dl>
      </div>
      <section class="termin" id="termin" aria-labelledby="termin-titel">
        <div class="verfuegbar">
          <h2 id="termin-titel"></h2>
          <p class="unter" id="termin-unter"></p>
          <div class="leistung" id="leistung" role="group" aria-label="Leistung wählen"></div>
          <div class="tage" id="tage" aria-label="Sprechtage dieser Woche"></div>
          <p class="zeiten-titel"><span id="zeiten-titel">Nächste freie Zeiten</span><a href="#">Alle Zeiten</a></p>
          <div class="zeiten" id="zeiten" role="group" aria-labelledby="zeiten-titel"></div>
          <div class="auswahl" id="auswahl" aria-live="polite"><div><p id="auswahl-text"></p></div></div>
          <button type="button" class="buchen" id="buchen" aria-disabled="true">Zeit wählen</button>
          <p class="zweiklick" id="zweiklick" hidden></p>
          <div class="oder" id="oder"></div>
        </div>
        <div class="abwesend" id="abwesend"></div>
      </section>
    </div>
  </div>

  <div class="huelle inhalt">
    <section aria-labelledby="sp-titel">
      <h2 id="sp-titel"></h2>
      <ol class="schwerpunkte" id="schwerpunkte"></ol>
      <div class="nur-stylistin"><h3 style="font-size:1.3rem;margin-top:28px">Arbeitsproben</h3><div class="proben" id="proben"></div></div>
    </section>
    <section aria-labelledby="wd-titel">
      <h2 id="wd-titel">Werdegang</h2>
      <dl class="werdegang" id="werdegang"></dl>
      <figure class="notiz"><blockquote id="notiz"></blockquote><figcaption id="notiz-von"></figcaption></figure>
    </section>
  </div>

  <div class="huelle team">
    <div class="team-kopf"><h2 id="team-titel"></h2><a href="#" id="team-alle"></a></div>
    <ul id="team"></ul>
  </div>

  <section class="huelle daten-panel" aria-label="Strukturierte Daten">
    <p>ProfilePage mit mainEntity Person, aus demselben Datensatz wie der sichtbare Text (steht auch im Kopf als script type="application/ld+json").</p>
    <pre id="ld-zeigen"></pre>
  </section>
</main>

<footer class="fuss"><div class="huelle" id="fuss"></div></footer>
<div class="leiste" id="leiste"></div>

<div class="pult" aria-label="Steuerung der Demo" role="group">
  <span class="mono">Branche</span>
  <div class="reihe" id="pult-v">
    <button type="button" data-v="aerztin" aria-pressed="true">Ärztin</button>
    <button type="button" data-v="stylistin" aria-pressed="false">Stylistin</button>
  </div>
  <button type="button" class="einzeln" id="pult-abwesend" aria-pressed="false"><span>Nicht verfügbar</span><span aria-hidden="true">aus</span></button>
  <button type="button" class="einzeln" id="pult-ld" aria-pressed="false"><span>JSON-LD zeigen</span><span aria-hidden="true">aus</span></button>
  <p class="werte" id="pult-werte"></p>
</div>

<script>
(() => {
  const h = document.documentElement;
  const reduziert = () => h.classList.contains('reduziert');
  const $ = (s, w = document) => w.querySelector(s);
  const $$ = (s, w = document) => [...w.querySelectorAll(s)];
  const TZ = 'Europe/Berlin';
  const fTag = new Intl.DateTimeFormat('de-DE', { timeZone: TZ, weekday: 'long', day: 'numeric', month: 'long' });
  const fKurz = new Intl.DateTimeFormat('de-DE', { timeZone: TZ, weekday: 'short', day: 'numeric', month: 'numeric' });
  const WT = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'];

  /* Ein Datensatz je Person: daraus entstehen Seite und JSON-LD */
  const PERSONEN = {
    aerztin: {
      betrieb: { name: 'Hausärzte am Mühlgraben', typ: 'MedicalClinic', ort: 'Neuruppin', strasse: 'Mühlgraben 6', plz: '16816', tel: '03391 40 27 15', telLink: '+493391402715', url: 'https://hausaerzte-muehlgraben.de' },
      marke: '<svg viewBox="0 0 32 32" aria-hidden="true"><circle cx="16" cy="16" r="15" fill="#005450"/><path d="M6 19c4-4 6 2 10-2s6 2 10-2" stroke="#ffc776" stroke-width="2.2" fill="none" stroke-linecap="round"/><path d="M6 13c4-4 6 2 10-2s6 2 10-2" stroke="#fff" stroke-width="2.2" fill="none" stroke-linecap="round" opacity=".7"/></svg>',
      nav: ['Praxis', 'Team', 'Sprechzeiten', 'Leistungen', 'Kontakt'], handlung: 'Termin', teamLabel: 'Team', anrede: 'Sie',
      praefix: 'Dr. med.', name: 'Ayşe Demirtaş', kurzname: 'Dr. Demirtaş', initialen: 'AD', slug: 'ayse-demirtas',
      rolle: 'Fachärztin für Allgemeinmedizin', stufe: 'Hausärztin',
      zustaendig: 'Zu ihr kommen Sie mit Diabetes, hohem Blutdruck, vor einer Fernreise und mit allem, was eine Hausärztin für die ganze Familie macht.',
      fakten: [['Sprachen', null], ['Kasse', 'alle Kassen und privat'], ['Neue Patienten', 'ja, nach kurzem Erstgespräch']],
      sprachen: [['de', 'Deutsch', ''], ['tr', 'Türkçe', 'Türkisch'], ['en', 'English', 'Englisch']],
      sprechtage: { 1: '8–12', 2: '8–12, 15–18', 4: '8–12, 15–19', 5: '8–12' }, slotMin: 20,
      leistungen: null,
      terminTitel: 'Termin bei Dr. Demirtaş', terminUnter: 'Sprechstunde, 20 Minuten. Für Vorsorge und Impfberatung bitte anrufen.',
      oder: '<span>Oder anrufen</span><a href="tel:+493391402715" class="ziffern">03391 40 27 15</a><small>Akut krank? Offene Sprechstunde Mo bis Fr 8 bis 9 Uhr, ohne Termin. Nachts und am Wochenende: 116 117.</small>',
      zweiklick: 'Die Buchung läuft über einen externen Terminanbieter. Er lädt erst nach diesem Klick.',
      abwesend: '<h2>Dr. Demirtaş ist in Elternzeit</h2><p class="hinweis">Bis Ende März 2027. In dieser Zeit betreut Dr. Jonas Ebeling ihre Patientinnen und Patienten, mit Zugriff auf alle Unterlagen.</p><a class="vertretung" href="#"><span class="mini" aria-hidden="true">JE</span><span><b>Dr. Jonas Ebeling</b><span>Facharzt für Innere Medizin · Termine Mo bis Fr</span></span></a><div class="oder"><span>Oder anrufen</span><a href="tel:+493391402715" class="ziffern">03391 40 27 15</a></div>',
      spTitel: 'Schwerpunkte',
      schwerpunkte: [
        ['Zucker im Blick', 'Diabetes Typ 2', 'Werte verstehen, Tabletten oder Insulin einstellen, Füße und Augen regelmäßig prüfen. Mit Schulung durch Tanja Rehse.'],
        ['Hoher Blutdruck', 'Hypertonie', 'Messen zu Hause statt nur in der Praxis, Medikamente so wenig wie möglich, so viel wie nötig.'],
        ['Vor der Reise', 'Reisemedizin', 'Impfungen und Reiseapotheke, am besten sechs Wochen vor der Abfahrt.'],
        ['Vorsorge ab 35', 'Check-up', 'Alle drei Jahre, Kassenleistung. Mit Blutwerten und Gespräch über Ihre Risiken.']
      ],
      werdegang: [['seit 2021', 'Hausärzte am Mühlgraben, Neuruppin'], ['2016–2021', 'Weiterbildung Allgemeinmedizin, Brandenburg an der Havel'], ['2015', 'Approbation, Promotion an der Charité Berlin'], ['Zusatz', 'Reisemedizin, Diabetes-Schulung']],
      notiz: '„Ich frage lieber einmal mehr nach. Und ich erkläre so, dass Sie es Ihrer Familie weitererzählen können.“', notizVon: 'Ayşe Demirtaş über ihre Sprechstunde',
      teamTitel: 'Auch in der Praxis', teamAlle: 'Ganzes Team',
      team: [['JE', 'Dr. Jonas Ebeling', 'Innere Medizin'], ['TR', 'Tanja Rehse', 'Diabetesberaterin'], ['MK', 'Mirko Kalisch', 'Praxisleitung, Empfang']],
      fuss: '<div><b>Hausärzte am Mühlgraben</b>Mühlgraben 6<br>16816 Neuruppin</div><div><b>Telefon</b><a href="tel:+493391402715" class="ziffern">03391 40 27 15</a><br>Notdienst 116 117, Lebensgefahr 112</div><div><b>Zugang</b>Erdgeschoss, stufenlos, Parkplatz am Haus</div><ul><li><a href="#">Impressum</a></li><li><a href="#">Datenschutz</a></li><li><a href="#">Barrierefreiheit</a></li></ul>',
      portraet: ['#cfe1dd', '#005450', '#ffc776'], werte: 'praxis · cubic-bezier(0.2,0,0.38,0.9) 150/250 ms · Porträt 700 ms (0,0,0.3,1) · Termin 20 min'
    },
    stylistin: {
      betrieb: { name: 'Salon Wollweber', typ: 'HairSalon', ort: 'Rotenburg (Wümme)', strasse: 'Große Straße 41', plz: '27356', tel: '04261 98 66 02', telLink: '+494261986602', url: 'https://salon-wollweber.de' },
      marke: '<svg viewBox="0 0 32 32" aria-hidden="true"><rect width="32" height="32" rx="16" fill="#1e1412"/><path d="M10 9c0 8 12 6 12 14M22 9c0 8-12 6-12 14" stroke="#f4e9e4" stroke-width="2" fill="none"/><circle cx="16" cy="16" r="2.2" fill="#9b1d2e"/></svg>',
      nav: ['Salon', 'Team', 'Preise', 'Looks', 'Gutschein'], handlung: 'Termin buchen', teamLabel: 'Team', anrede: 'du',
      praefix: '', name: 'Lene Marquardt', kurzname: 'Lene', initialen: 'LM', slug: 'lene-marquardt',
      rolle: 'Senior-Stylistin', stufe: 'Locken und Farbe',
      zustaendig: 'Lene schneidet Locken trocken, Strähne für Strähne, und färbt Balayage, die auch nach drei Monaten noch gut herauswächst.',
      fakten: [['Sprachen', null], ['Preis', 'Schnitt bei Lene ab 68 €, je nach Länge'], ['Im Salon', 'Di bis Sa']],
      sprachen: [['de', 'Deutsch', ''], ['en', 'English', 'Englisch'], ['pl', 'Polski', 'Polnisch']],
      sprechtage: { 2: '10–19', 3: '10–19', 4: '12–20', 5: '10–19', 6: '9–15' }, slotMin: 30,
      leistungen: [['locken', 'Lockenschnitt', 75], ['balayage', 'Balayage', 180], ['pony', 'Pony nachschneiden', 15]],
      terminTitel: 'Bei Lene buchen', terminUnter: 'Wähl erst, was du machen lässt. Dann siehst du ihre freien Zeiten.',
      oder: '<span>Lieber anrufen?</span><a href="tel:+494261986602" class="ziffern">04261 98 66 02</a><small>Di bis Fr 10 bis 19 Uhr, Sa 9 bis 15 Uhr</small>',
      zweiklick: 'Der Kalender kommt von einem externen Buchungsanbieter und lädt erst nach diesem Klick.',
      abwesend: '<h2>Lene ist im Urlaub</h2><p class="hinweis">Bis 30. November. Für Locken bucht dich gern Kaja Thielsen, sie hat bei Lene gelernt.</p><a class="vertretung" href="#"><span class="mini" aria-hidden="true">KT</span><span><b>Kaja Thielsen</b><span>Stylistin · Locken, Herrenschnitt</span></span></a><div class="oder"><span>Lieber anrufen?</span><a href="tel:+494261986602" class="ziffern">04261 98 66 02</a></div>',
      spTitel: 'Was Lene am liebsten macht',
      schwerpunkte: [
        ['Locken trocken schneiden', 'Curly Cut', 'Sie schneidet jede Locke dort, wo sie fällt. Danach zeigt sie dir, wie du sie zu Hause formst.'],
        ['Balayage', 'freihändig gemalt', 'Weiche Übergänge ohne harte Ansätze. Nachfärben meist erst nach drei bis vier Monaten.'],
        ['Farbe korrigieren', 'Farbkorrektur', 'Wenn eine Farbe nicht geworden ist, was sie sollte. Erst ein Gespräch, dann ein Plan, oft in zwei Terminen.']
      ],
      werdegang: [['seit 2019', 'Salon Wollweber, Rotenburg'], ['2017–2019', 'Salon in Bremen, Schwerpunkt Farbe'], ['2016', 'Meisterprüfung Friseurhandwerk'], ['Kurse', 'Lockenschnitt, Balayage-Technik']],
      notiz: '„Ich will, dass dein Haar auch am dritten Tag nach dem Termin noch nach dir aussieht.“', notizVon: 'Lene über ihre Arbeit',
      teamTitel: 'Auch im Salon', teamAlle: 'Ganzes Team',
      team: [['KT', 'Kaja Thielsen', 'Stylistin'], ['BW', 'Birte Wollweber', 'Inhaberin, Meisterin'], ['NS', 'Nils Spreckelsen', 'Junior-Stylist']],
      fuss: '<div><b>Salon Wollweber</b>Große Straße 41<br>27356 Rotenburg (Wümme)</div><div><b>Telefon</b><a href="tel:+494261986602" class="ziffern">04261 98 66 02</a></div><div><b>Zugang</b>Eine Stufe am Eingang, Rampe auf Nachfrage</div><ul><li><a href="#">Impressum</a></li><li><a href="#">Datenschutz</a></li><li><a href="#">Barrierefreiheit</a></li></ul>',
      portraet: ['#e9dbd4', '#1e1412', '#9b1d2e'], werte: 'friseur · cubic-bezier(0.625,0.05,0,1) 250/400 ms, zu 260 ms · Porträt 725 ms'
    }
  };

  let v = new URLSearchParams(location.search).get('v');
  if (!PERSONEN[v]) v = 'aerztin';
  let gewaehlt = null, leistung = null;

  function portraetSvg(p) {
    const [hell, dunkel, akz] = p.portraet;
    return `<svg viewBox="0 0 400 500" preserveAspectRatio="xMidYMid slice" aria-hidden="true"><defs><linearGradient id="lg" x1="0" y1="0" x2=".8" y2="1"><stop offset="0" stop-color="${hell}"/><stop offset="1" stop-color="${dunkel}" stop-opacity=".22"/></linearGradient></defs><rect width="400" height="500" fill="url(#lg)"/><path d="M60 500V230a140 140 0 0 1 280 0v270" fill="${dunkel}" opacity=".09"/><rect x="250" y="40" width="96" height="150" rx="48" fill="#fff" opacity=".42"/><circle cx="312" cy="420" r="22" fill="${akz}" opacity=".8"/><text x="200" y="300" text-anchor="middle" font-family="${v === 'stylistin' ? 'Kalnia' : 'Hedvig Letters Serif'}, serif" font-size="150" fill="${dunkel}" opacity=".85">${p.initialen}</text></svg><figcaption class="ph"><span>Porträt folgt</span><span>4:5</span></figcaption>`;
  }

  // Sprechtage → nächste freie Zeiten (im Einsatz aus der Buchungs-Schnittstelle)
  function berlinHeute() { const p = Object.fromEntries(new Intl.DateTimeFormat('en-CA', { timeZone: TZ, year: 'numeric', month: '2-digit', day: '2-digit' }).formatToParts(new Date()).map(x => [x.type, x.value])); return new Date(Date.UTC(+p.year, +p.month - 1, +p.day, 12)); }
  function freieZeiten(p, dauer) {
    const out = []; let d = berlinHeute(), n = 0;
    while (out.length < 6 && n < 21) {
      d = new Date(d.getTime() + 864e5); n++;
      const spannen = p.sprechtage[d.getUTCDay()]; if (!spannen) continue;
      spannen.split(', ').forEach((sp, si) => {
        const [a, b] = sp.split('–').map(Number);
        const kandidaten = [];
        for (let m = a * 60; m + dauer <= b * 60; m += p.slotMin) kandidaten.push(m);
        // zwei Lücken je Spanne, deterministisch aus dem Datum
        const s = (d.getUTCDate() * 7 + si * 3 + dauer) % Math.max(1, kandidaten.length);
        [kandidaten[s], kandidaten[(s + 5) % kandidaten.length]].forEach(m => { if (m != null && out.length < 6 && !out.some(o => o.d.getTime() === d.getTime() && o.m === m)) out.push({ d, m }); });
      });
    }
    return out.sort((x, y) => x.d - y.d || x.m - y.m).slice(0, 6);
  }
  const uhr = m => `${Math.floor(m / 60)}:${String(m % 60).padStart(2, '0')}`;

  function jsonLd(p) {
    const b = p.betrieb;
    const person = { '@type': 'Person', name: (p.praefix ? p.praefix + ' ' : '') + p.name, givenName: p.name.split(' ')[0], familyName: p.name.split(' ').slice(1).join(' '), jobTitle: p.rolle, knowsLanguage: p.sprachen.map(s => s[0]), worksFor: { '@type': b.typ, name: b.name, telephone: b.telLink, address: { '@type': 'PostalAddress', streetAddress: b.strasse, postalCode: b.plz, addressLocality: b.ort, addressCountry: 'DE' } }, url: `${b.url}/team/${p.slug}` };
    if (p.praefix) person.honorificPrefix = p.praefix;
    return { '@context': 'https://schema.org', '@graph': [
      { '@type': 'ProfilePage', url: `${b.url}/team/${p.slug}`, dateModified: '2026-11-04', mainEntity: person },
      { '@type': 'BreadcrumbList', itemListElement: [['Startseite', b.url], [p.teamLabel, b.url + '/team'], [person.name, `${b.url}/team/${p.slug}`]].map(([n, u], i) => ({ '@type': 'ListItem', position: i + 1, name: n, item: u })) }
    ] };
  }

  function zeiten() {
    const p = PERSONEN[v];
    const dauer = p.leistungen ? (p.leistungen.find(l => l[0] === leistung) || [0, 0, 0])[2] : p.slotMin;
    const box = $('#zeiten');
    if (p.leistungen && !leistung) { box.innerHTML = '<p style="grid-column:1/-1;font-size:15px;color:var(--leise)">Erst eine Leistung wählen.</p>'; return; }
    box.innerHTML = freieZeiten(p, dauer).map(z => { const lang = `${fTag.format(z.d)}, ${uhr(z.m)} Uhr`; return `<button type="button" class="zeit" aria-pressed="false" data-zeit="${lang}" aria-label="${lang}"><span>${fKurz.format(z.d).replace(',', '')}</span><b class="ziffern">${uhr(z.m)}</b></button>`; }).join('');
    $$('.zeit', box).forEach(b => b.addEventListener('click', () => waehle(b)));
  }
  function waehle(b) {
    const p = PERSONEN[v];
    $$('.zeit').forEach(x => x.setAttribute('aria-pressed', x === b));
    gewaehlt = b.dataset.zeit;
    const l = p.leistungen ? p.leistungen.find(x => x[0] === leistung) : null;
    $('#auswahl-text').textContent = `${gewaehlt} · ${l ? l[1] + ', ' + l[2] + ' Minuten' : 'Sprechstunde, 20 Minuten'} · bei ${p.kurzname}`;
    $('#auswahl').classList.add('offen');
    const bu = $('#buchen'); bu.removeAttribute('aria-disabled'); bu.textContent = p.anrede === 'du' ? 'Weiter zur Buchung' : 'Weiter zur Buchung';
    $('#zweiklick').hidden = false;
  }

  function zeichne(anim) {
    const p = PERSONEN[v], b = p.betrieb;
    h.dataset.v = v;
    document.title = `${(p.praefix ? p.praefix + ' ' : '') + p.name} · ${b.name} · Fundus-Demo`;
    $('#marke').innerHTML = `${p.marke}<span><b>${b.name}</b><small>${b.ort}</small></span>`;
    $('#marke').setAttribute('aria-label', `${b.name}, Startseite`);
    $('#nav').innerHTML = p.nav.map(n => `<li><a href="#"${n === 'Team' ? ' aria-current="page"' : ''}>${n}</a></li>`).join('');
    $('#tel-kopf').textContent = b.tel; $('#tel-kopf').href = 'tel:' + b.telLink;
    $('#kopf-handlung').textContent = p.handlung;
    $('#krume-team').textContent = p.teamLabel;
    $('#krume').textContent = (p.praefix ? 'Dr. ' : '') + p.name;
    const por = $('#portraet'); por.innerHTML = portraetSvg(p);
    por.classList.remove('blendet'); if (anim && !reduziert()) { void por.offsetWidth; por.classList.add('blendet'); }
    $('#unterschrift').textContent = p.name.split(' ')[0];
    $('#rolle').textContent = p.rolle; $('#stufe').textContent = p.stufe;
    $('#titel').innerHTML = p.praefix ? `<span style="font-size:.62em;display:block;color:var(--leise)">${p.praefix}</span>${p.name}` : p.name;
    $('#zustaendig').textContent = p.zustaendig;
    $('#fakten').innerHTML = p.fakten.map(([a, x]) => `<div><dt>${a}</dt><dd>${x === null ? `<ul class="sprachen">${p.sprachen.map(([l, n, de]) => `<li><span lang="${l}">${n}</span>${de ? `<small>${de}</small>` : ''}</li>`).join('')}</ul>` : x}</dd></div>`).join('');
    // Termin
    $('#termin-titel').textContent = p.terminTitel; $('#termin-unter').textContent = p.terminUnter;
    const le = $('#leistung'); le.hidden = !p.leistungen;
    if (p.leistungen) { le.innerHTML = p.leistungen.map(([id, n, d]) => `<button type="button" data-l="${id}" aria-pressed="${leistung === id}">${n} <span class="ziffern" style="opacity:.75">${d < 60 ? d + ' Min.' : (d / 60).toString().replace('.', ',') + ' Std.'}</span></button>`).join(''); $$('button', le).forEach(x => x.addEventListener('click', () => { leistung = x.dataset.l; $$('button', le).forEach(y => y.setAttribute('aria-pressed', y === x)); zuruecksetzen(); zeiten(); })); }
    const heute = berlinHeute().getUTCDay();
    const tageIds = Object.keys(p.sprechtage).map(Number);
    const reihe = (p.leistungen ? [2, 3, 4, 5, 6] : [1, 2, 3, 4, 5]);
    $('#tage').innerHTML = reihe.map(t => `<div class="${p.sprechtage[t] ? '' : 'frei'}${t === heute ? ' heute' : ''}"><b>${WT[t]}</b><span class="ziffern">${p.sprechtage[t] ? p.sprechtage[t].replace(', ', '<br>') : 'frei'}</span>${t === heute ? '<span class="vh"> (heute)</span>' : ''}</div>`).join('');
    $('#oder').innerHTML = p.oder;
    $('#zweiklick').textContent = p.zweiklick;
    $('#abwesend').innerHTML = p.abwesend;
    zuruecksetzen(); zeiten();
    // Inhalt
    $('#sp-titel').textContent = p.spTitel;
    $('#schwerpunkte').innerHTML = p.schwerpunkte.map(([a, f, t], i) => `<li><span class="nr" aria-hidden="true">${i + 1}</span><div><b>${a}</b><span class="fach">${f}</span><p>${t}</p></div></li>`).join('');
    $('#proben').innerHTML = ['Lockenschnitt, schulterlang', 'Balayage, Honigblond', 'Korrektur, Kupfer'].map((t, i) => `<figure class="probe" style="background:linear-gradient(${160 + i * 25}deg, ${['#c79a7b', '#d8b78e', '#a85a3a'][i]}, ${['#5e3b2c', '#8c6a4a', '#5a2418'][i]})"><span>Foto folgt · ${t}</span></figure>`).join('');
    $('#werdegang').innerHTML = p.werdegang.map(([a, x]) => `<div><dt class="ziffern">${a}</dt><dd>${x}</dd></div>`).join('');
    $('#notiz').textContent = p.notiz; $('#notiz-von').textContent = p.notizVon;
    $('#team-titel').textContent = p.teamTitel; $('#team-alle').textContent = p.teamAlle;
    $('#team').innerHTML = p.team.map(([i, n, r]) => `<li><a class="person" href="#"><span class="mini" aria-hidden="true">${i}</span><span><b>${n}</b><span>${r}</span></span></a></li>`).join('');
    $('#fuss').innerHTML = p.fuss;
    $('#leiste').innerHTML = `<a href="tel:${b.telLink}">Anrufen</a><a class="haupt" href="#termin">${p.anrede === 'du' ? 'Bei ' + p.kurzname + ' buchen' : 'Termin bei ' + p.kurzname}</a>`;
    // JSON-LD aus demselben Datensatz
    const ld = jsonLd(p);
    $('#ld').textContent = JSON.stringify(ld);
    $('#ld-zeigen').textContent = JSON.stringify(ld, null, 2);
    $('#pult-werte').textContent = p.werte;
  }
  function zuruecksetzen() {
    gewaehlt = null; $('#auswahl').classList.remove('offen');
    const bu = $('#buchen'); bu.setAttribute('aria-disabled', 'true'); bu.textContent = 'Zeit wählen'; bu.classList.remove('druck');
    $('#zweiklick').hidden = true;
  }
  $('#buchen').addEventListener('click', () => { if (!gewaehlt) { const z = $('.zeit') || $('#leistung button'); if (z) z.focus(); return; } $('#zweiklick').textContent = 'Nachgebildet: Hier lädt jetzt das Buchungs-Widget mit der gewählten Zeit.'; });

  function setzeV(neu, schreiben) {
    v = neu; leistung = null;
    $$('#pult-v button').forEach(b => b.setAttribute('aria-pressed', b.dataset.v === v));
    if (schreiben) { const q = new URLSearchParams(location.search); q.set('v', v); history.replaceState(null, '', location.pathname + '?' + q); }
    zeichne(true);
  }
  $$('#pult-v button').forEach(b => b.addEventListener('click', () => setzeV(b.dataset.v, true)));
  function schalter(id, attr) {
    const b = $(id);
    b.addEventListener('click', () => { const an = b.getAttribute('aria-pressed') !== 'true'; b.setAttribute('aria-pressed', an); b.lastElementChild.textContent = an ? 'an' : 'aus'; h.toggleAttribute(attr, an); });
  }
  schalter('#pult-abwesend', 'data-abwesend');
  schalter('#pult-ld', 'data-ld');
  const q0 = new URLSearchParams(location.search);
  if (q0.has('abwesend')) $('#pult-abwesend').click();
  if (q0.has('ld')) $('#pult-ld').click();
  setzeV(v, false);

  /* Vorführung: Leistung (Salon), zwei Zeiten wählen, weiter zur Buchung */
  let uhren = [];
  const spaeter = (f, ms) => uhren.push(setTimeout(f, ms));
  function stopp() { uhren.forEach(clearTimeout); uhren = []; }
  function neustart() { stopp(); leistung = null; zeichne(true); }
  function vorfuehren() {
    neustart();
    h.classList.add('vorfuehrung');
    let t = 1200;
    if (PERSONEN[v].leistungen) { spaeter(() => { const b = $('#leistung button'); if (b) b.click(); }, t); t += 1300; }
    spaeter(() => { const z = $$('.zeit'); if (z[0]) waehle(z[0]); }, t);
    spaeter(() => { const z = $$('.zeit'); if (z[3]) waehle(z[3]); }, t + 1600);
    spaeter(() => { const bu = $('#buchen'); bu.classList.add('druck'); spaeter(() => { bu.classList.remove('druck'); bu.click(); }, 240); }, t + 3200);
    spaeter(vorfuehren, t + 6400);
  }
  function pause() { stopp(); h.classList.remove('vorfuehrung'); }
  function weiter() { vorfuehren(); }

})();
</script>
</body>
</html>