# 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 `