diff --git a/docs/adr/0014-tour-system-mit-elevenlabs-voice.md b/docs/adr/0014-tour-system-mit-elevenlabs-voice.md new file mode 100644 index 0000000..57220d2 --- /dev/null +++ b/docs/adr/0014-tour-system-mit-elevenlabs-voice.md @@ -0,0 +1,183 @@ +# 0014 — Tour-System mit ElevenLabs-Voice + Web-Speech-Fallback + +| | | +|---|---| +| **Status** | accepted | +| **Datum** | 2026-05-09 | +| **Refs** | #185, Commits 1c74cb8 → bf6201e | + +## Kontext + +Aus Issue #185 ("Bürger:innen-Perspektive: Was überfordert? Profi-Infos +hinter Klick verstecken") kam die User-Anforderung, den Profi-Modus +(v3-Antrag-Detail mit GWÖ-Score, Matrix 5×5, Marker-Pillen) für +Erst-Besucher:innen erklärbar zu machen — **ohne** den Profi-Modus +selbst zu vereinfachen. + +Anforderung wurde im Verlauf konkretisiert: + +1. Schaltfläche „Tour" auf jeder Seite außer Administration. +2. „Du bist neu hier?"-Banner auf der Startseite, einmalig per + localStorage dismissable; Topbar-Tour-Link bleibt permanent. +3. Pro Menüpunkt **eigene Stationen**, die die Funktionalität dieser + Page erklären (nicht eine generische Erklärung des Layouts). +4. Sprachausgabe: weibliche, freundliche, warme, seriöse Stimme. + Phase 1: Web Speech API als MVP. Phase 2: ElevenLabs (Domi). +5. Ermächtigend formulieren, nicht vereinfachend — Bürger:innen sollen + das Profi-Tool **lesen können**, nicht eine reduzierte Variante + bekommen. + +## Optionen + +### Option A — Externe Tour-Library (intro.js, shepherd.js, driver.js) + +Etabliertes Library-Ökosystem mit Spotlight-Effekt und Tour-Choreografie. + +**Vorteile:** weniger eigener Code, vertraute UX-Patterns. +**Nachteile:** zusätzliche JS-Dependency (~30–80 KB gezippt), keine +Audio-Integration, kein bürgernahes Wording out of the box, Styling +anders als das übrige v2/v3-Design. + +### Option B — Eigene Spotlight-Engine, page-spezifische Stations + +Eine schlanke Tour-Engine in `app/templates/v3/components/tour.html`, +~250 Zeilen JS + CSS. Stationen pro Page über `window.GWOE_TOUR_STEPS` +gesetzt. Engine löst Selektoren zur Laufzeit auf und überspringt +Stations, deren Element nicht im DOM ist (z.B. keine Plenum-Votes auf +einer bestimmten Drucksache). + +**Vorteile:** keine externe Dependency, volle Kontrolle über Wording, +eigenes Design (pill-shaped Buttons, teal-Farbgebung), enge +Integration in CSP/Auth-Logik. +**Nachteile:** Engine-Code muss selbst gepflegt werden. + +### Option C — Server-seitig generierte Tour-Texte pro User-Profil + +Backend bestimmt anhand des User-Profils (Erst-Visit, Eingeloggt, +Bundesland-Filter), welche Stationen geladen werden. Mehr Personalisierung. + +**Vorteile:** maximale Anpassung. +**Nachteile:** über-engineered für den Erst-Aufschlag; macht jede +Tour-Iteration zum Server-Deploy. + +## Entscheidung + +**Option B** mit folgender Architektur: + +### Engine + +`app/templates/v3/components/tour.html` enthält UI (Overlay, Spotlight, +Bubble, Mute-Toggle), Engine-Logik und CSS. Wird global in +`v2/base.html` per Jinja-`{% include %}` eingebunden, nur ausgeschlossen +für Administration: + +```jinja +{% if v2_active_nav not in ['admin_stand', 'admin_queue', 'admin_abos', + 'freischaltungen', 'queue', 'abos'] %} +{% include "v3/components/tour.html" ignore missing %} +{% endif %} +``` + +### Stations pro Page + +Jedes Page-Template definiert seine Stationen am Anfang des +`body_scripts`-Blocks: + +```html + +``` + +Selektoren mit Komma-separierter Fallback-Liste; Engine nimmt das erste +Match und überspringt Stations ohne Match. Damit funktioniert die Tour +auch, wenn ein Element auf manchen Drucksachen fehlt. + +Auth-spezifische Stations via Jinja: z.B. die Startseite hat +unterschiedliche „Navigation links"-Texte je nach `is_authenticated`, +weil die Sidebar unterschiedliche Items zeigt. + +### Fallback + +Pages ohne `window.GWOE_TOUR_STEPS` bekommen drei generische Stations +(Logo+Konzept, Topbar, Sidebar) — damit der Topbar-Tour-Link überall +einen Inhalt zeigt. + +### Audio: zwei Pfade + +**Phase 1** (Web Speech API): Browser-eingebaute TTS via +`speechSynthesis`. Funktioniert ohne API-Key, klingt mechanisch. + +**Phase 2** (ElevenLabs/Domi): + +- Backend-Endpoint `GET /api/tour/voice?text=…`, ENV-konfiguriert + via `ELEVENLABS_API_KEY` und `ELEVENLABS_VOICE_ID` + (Default: Domi `AZnzlk1XvdvUeBnXmlld`). +- Server-seitiges Caching: `data/tour_audio/.mp3`. + Folgeabrufe gehen aus dem Datei-Cache, kein API-Quota-Verbrauch. +- Frontend: `speak()` versucht erst den Endpoint. Bei 503 (kein API-Key) + oder Fehler Fallback auf Web Speech. +- **Auto-Play-Block-Workaround**: persistentes `