diff --git a/app/main.py b/app/main.py
index 3ceeac3..dbc2ce6 100644
--- a/app/main.py
+++ b/app/main.py
@@ -118,12 +118,19 @@ class SecurityHeadersMiddleware(BaseHTTPMiddleware):
response.headers["X-XSS-Protection"] = "1; mode=block"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
response.headers["Permissions-Policy"] = "geolocation=(), microphone=(), camera=()"
- # CSP: Allow self, inline styles (for templates), and PDF viewer
+ # CSP: Allow self, inline styles (for templates), and PDF viewer.
+ # media-src: 'self' für die Eigen-Hosting-MP3s (Tour-Audio aus
+ # ElevenLabs-Cache via /api/tour/voice), data: für das 1×1
+ # silent-WAV zum Audio-Element-Unlock im Click-Handler, blob:
+ # für die im Browser zwischengespeicherten ElevenLabs-Blobs
+ # (Object URLs via URL.createObjectURL). Ohne media-src
+ # blockt der Browser jeden Audio-play-Versuch (#185 Phase 2).
response.headers["Content-Security-Policy"] = (
"default-src 'self'; "
"style-src 'self' 'unsafe-inline'; "
"script-src 'self' 'unsafe-inline'; "
"img-src 'self' data:; "
+ "media-src 'self' data: blob:; "
"frame-ancestors 'none';"
)
return response
diff --git a/app/templates/v2/base.html b/app/templates/v2/base.html
index 1294b12..7ff9e51 100644
--- a/app/templates/v2/base.html
+++ b/app/templates/v2/base.html
@@ -226,6 +226,16 @@
{% block body_scripts %}{% endblock %}
+{# ── Tour-Engine — auf jeder Seite eingehängt (#185).
+ Pages, die eigene Stationen wollen, setzen ``window.GWOE_TOUR_STEPS``
+ im body_scripts-Block oben. Pages ohne Stationen lassen den Topbar-
+ Tour-Link automatisch ausgeblendet. Administration ist explizit
+ ausgenommen. #}
+{% if v2_active_nav not in ['admin_stand', 'admin_queue', 'admin_abos',
+ 'freischaltungen', 'queue', 'abos'] %}
+{% include "v3/components/tour.html" ignore missing %}
+{% endif %}
+
{# ── Globaler BL-Selector — Persistenz + Event ───────────────────────────── #}
-{% include "v3/components/tour.html" %}
{% endblock %}
diff --git a/app/templates/v3/components/tour.html b/app/templates/v3/components/tour.html
index 4181d8a..1090b40 100644
--- a/app/templates/v3/components/tour.html
+++ b/app/templates/v3/components/tour.html
@@ -141,8 +141,27 @@
// Jeder Eintrag: {selector, title, text}. Selectoren werden zur Laufzeit
// aufgelöst — fehlt das Element (z.B. keine Plenum-Votes auf einer
// bestimmten Drucksache), überspringt die Tour den Schritt automatisch.
+ //
+ // Fallback: pages ohne eigene Stationen bekommen eine kurze
+ // Orientierungs-Tour, damit die Tour-Schaltfläche überall einen
+ // sinnvollen Inhalt zeigt.
+ const FALLBACK_STEPS = [
+ { selector: '.v2-brand-link, .v2-brand',
+ title: 'Willkommen beim GWÖ-Antragsprüfer',
+ text: 'Diese Seite bewertet Anträge aus deutschen Parlamenten nach der Gemeinwohl-Matrix. Was sie auszeichnet: Sie schaut nicht nur, ob ein Antrag „gut klingt", sondern wie sehr er fünf Werten dient — Würde, Solidarität, Nachhaltigkeit, Gerechtigkeit und Demokratie. Klick auf das Logo bringt dich jederzeit zur Übersicht zurück.' },
+ { selector: '.v2-topbar',
+ title: 'Topbar',
+ text: 'Oben findest du Methodik (wie wir bewerten), Quellen (alle Wahl- und Grundsatzprogramme, semantisch durchsuchbar) und den Bundesland-Filter. Rechts ist der Theme-Toggle und — falls eingeloggt — dein Profil.' },
+ { selector: '#v2-sidebar nav',
+ title: 'Navigation links',
+ text: 'Links findest du die Hauptbereiche. Die Liste passt sich an: angemeldete Nutzer:innen sehen außerdem Auswertungen, Stimmverhalten und persönliche Funktionen wie eine Merkliste.' },
+ ];
+
function getSteps() {
- return Array.isArray(window.GWOE_TOUR_STEPS) ? window.GWOE_TOUR_STEPS : [];
+ if (Array.isArray(window.GWOE_TOUR_STEPS) && window.GWOE_TOUR_STEPS.length > 0) {
+ return window.GWOE_TOUR_STEPS;
+ }
+ return FALLBACK_STEPS;
}
let _tourIdx = 0;
diff --git a/app/templates/v3/screens/antrag_detail.html b/app/templates/v3/screens/antrag_detail.html
index 6d77e80..467ed50 100644
--- a/app/templates/v3/screens/antrag_detail.html
+++ b/app/templates/v3/screens/antrag_detail.html
@@ -565,7 +565,7 @@
text: 'Hier sehen Sie, wie die Fraktionen tatsächlich abgestimmt haben. Das Warnschild neben einer Fraktion bedeutet Heuchelei: Sie stimmt mit Nein, obwohl der Antrag exakt zu ihrem eigenen Wahlprogramm passt. Das Ausrufezeichen markiert Opportunismus: Ja-Stimme bei einem Antrag, der dem eigenen Programm widerspricht.' },
];
- {% include "v3/components/tour.html" %}
+ {# Tour-Engine wird global in v2/base.html eingebunden #}
{# .v3-page #}
{% endif %}{# antrag #}