fix(tour, nav): Audio-Auto-Play entsperren + Daten-Nav für alle sichtbar
Drei zusammenhängende UI-Bugs: 1) Audio kam nicht — Browser-Auto-Play-Block. ``new Audio(blobUrl).play()`` nach einem await zählt nicht mehr als User-Gesture; Safari/Chrome kassieren mit NotAllowedError. Fix: persistentes <audio>-Element wird einmal beim Tour-Start (im Click-Handler, synchron) mit einer 1×1-silent-WAV entsperrt. Folgende src-Updates spielen ohne Block. 2) Tour-Bubble „Weiter"-Button sah 90er aus — der lokale CSS-Override ``.gwoe-tour-bubble .v3-action-btn.primary`` hat den modernen pill-shaped Style ausgehebelt. Override entfernt; nutzt jetzt das globale ``.v3-action-btn.primary`` (teal-solid, runde Ecken, weicher Drop-Shadow). 3) Tour erzählt anonymen User:innen über „Auswertungen" und „Stimmverhalten", die in der linken Nav für Anonyme nicht sichtbar waren. Aggregierte Daten sind öffentlich — Daten-Nav-Gruppe jetzt für alle sichtbar (Auswertungen, Stimmverhalten, Aktuelle Themen, Export-API, Atom-Feed). Persönliche Items (Merkliste, Abos, Neuer Antrag, Batch) bleiben eingeloggt. Cluster + Landtag-Suche bleiben eingeloggt/admin (Backend-Routen sind ohnehin require_auth).
This commit is contained in:
parent
a3a2b90e9f
commit
e397ae5028
@ -42,10 +42,20 @@
|
|||||||
<span class="v2-nav-count">{{ assessment_count }}</span>
|
<span class="v2-nav-count">{{ assessment_count }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
{% if is_authenticated %}<a href="/v2/merkliste" class="v2-nav-item {% if v2_active_nav == 'merkliste' %}active{% endif %}">{{ icon("bookmark-simple", 14) }} Merkliste</a>{% endif %}
|
|
||||||
<a href="/v2/tags" class="v2-nav-item {% if v2_active_nav == 'tags' %}active{% endif %}">{{ icon("tag", 14) }} Tags</a>
|
<a href="/v2/tags" class="v2-nav-item {% if v2_active_nav == 'tags' %}active{% endif %}">{{ icon("tag", 14) }} Tags</a>
|
||||||
{% if is_admin %}<a href="/v2/cluster" class="v2-nav-item {% if v2_active_nav == 'cluster' %}active{% endif %}">{{ icon("graph", 14) }} Cluster</a>{% endif %}
|
{% if is_admin %}<a href="/v2/cluster" class="v2-nav-item {% if v2_active_nav == 'cluster' %}active{% endif %}">{{ icon("graph", 14) }} Cluster</a>{% endif %}
|
||||||
{% if is_authenticated %}<a href="/v2/landtag-suche" class="v2-nav-item {% if v2_active_nav == 'landtag_suche' %}active{% endif %}">{{ icon("magnifying-glass-plus", 14) }} Landtag-Suche</a>{% endif %}
|
{% if is_authenticated %}<a href="/v2/landtag-suche" class="v2-nav-item {% if v2_active_nav == 'landtag_suche' %}active{% endif %}">{{ icon("magnifying-glass-plus", 14) }} Landtag-Suche</a>{% endif %}
|
||||||
|
{% if is_authenticated %}<a href="/v2/merkliste" class="v2-nav-item {% if v2_active_nav == 'merkliste' %}active{% endif %}">{{ icon("bookmark-simple", 14) }} Merkliste</a>{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# ── Daten — aggregiert, öffentlich für alle sichtbar ───────── #}
|
||||||
|
<div class="v2-nav-group">
|
||||||
|
<div class="v2-nav-label">— Daten</div>
|
||||||
|
<a href="/auswertungen" class="v2-nav-item {% if v2_active_nav == 'auswertungen' %}active{% endif %}">{{ icon("chart-bar", 14) }} Auswertungen</a>
|
||||||
|
<a href="/stimmverhalten" class="v2-nav-item {% if v2_active_nav == 'stimmverhalten' %}active{% endif %}">{{ icon("circle-half", 14) }} Stimmverhalten</a>
|
||||||
|
<a href="/aktuelle-themen" class="v2-nav-item {% if v2_active_nav == 'aktuelle-themen' %}active{% endif %}">{{ icon("book-open", 14) }} Aktuelle Themen</a>
|
||||||
|
<a href="/api/auswertungen/export.csv" class="v2-nav-item">{{ icon("file-csv", 14) }} Export · API</a>
|
||||||
|
<a href="/v2/feed" class="v2-nav-item {% if v2_active_nav == 'feed' %}active{% endif %}">{{ icon("rss", 14) }} Atom-Feed</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if is_authenticated %}
|
{% if is_authenticated %}
|
||||||
@ -56,12 +66,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="v2-nav-group">
|
<div class="v2-nav-group">
|
||||||
<div class="v2-nav-label">— Daten</div>
|
<div class="v2-nav-label">— Persönlich</div>
|
||||||
<a href="/auswertungen" class="v2-nav-item {% if v2_active_nav == 'auswertungen' %}active{% endif %}">{{ icon("chart-bar", 14) }} Auswertungen</a>
|
|
||||||
<a href="/stimmverhalten" class="v2-nav-item {% if v2_active_nav == 'stimmverhalten' %}active{% endif %}">{{ icon("circle-half", 14) }} Stimmverhalten</a>
|
|
||||||
<a href="/aktuelle-themen" class="v2-nav-item {% if v2_active_nav == 'aktuelle-themen' %}active{% endif %}">{{ icon("book-open", 14) }} Aktuelle Themen</a>
|
|
||||||
<a href="/api/auswertungen/export.csv" class="v2-nav-item">{{ icon("file-csv", 14) }} Export · API</a>
|
|
||||||
<a href="/v2/feed" class="v2-nav-item {% if v2_active_nav == 'feed' %}active{% endif %}">{{ icon("rss", 14) }} Atom-Feed</a>
|
|
||||||
<a href="/v2/abos" class="v2-nav-item {% if v2_active_nav == 'abos' %}active{% endif %}">{{ icon("envelope-simple", 14) }} Meine Abos</a>
|
<a href="/v2/abos" class="v2-nav-item {% if v2_active_nav == 'abos' %}active{% endif %}">{{ icon("envelope-simple", 14) }} Meine Abos</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -119,11 +119,8 @@
|
|||||||
}
|
}
|
||||||
.gwoe-tour-spacer { flex: 1 1 auto; }
|
.gwoe-tour-spacer { flex: 1 1 auto; }
|
||||||
.gwoe-tour-mute { font-size: 12px; }
|
.gwoe-tour-mute { font-size: 12px; }
|
||||||
.gwoe-tour-bubble .v3-action-btn.primary {
|
/* primary-Override entfernt — verwendet jetzt das globale .v3-action-btn.primary
|
||||||
background: var(--ecg-teal);
|
Styling (pill-shaped, teal-solid, weicher Drop-Shadow). */
|
||||||
color: #fff;
|
|
||||||
border-color: var(--ecg-teal);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 700px) {
|
@media (max-width: 700px) {
|
||||||
.gwoe-tour-bubble {
|
.gwoe-tour-bubble {
|
||||||
@ -152,11 +149,48 @@
|
|||||||
let _resolvedSteps = []; // STEPS gefiltert auf vorhandene Elemente
|
let _resolvedSteps = []; // STEPS gefiltert auf vorhandene Elemente
|
||||||
let _tourMuted = false;
|
let _tourMuted = false;
|
||||||
let _tourUtter = null;
|
let _tourUtter = null;
|
||||||
let _tourAudio = null; // <audio>-Element für ElevenLabs-MP3
|
// Persistentes <audio>-Element. Wir entsperren es einmal beim
|
||||||
|
// Tour-Start (User-Gesture vom Click) mit einer leeren src + play(),
|
||||||
|
// damit spätere src-Updates ohne Auto-Play-Block laufen — sonst
|
||||||
|
// kassieren Safari/Chrome jedes ``new Audio(blobUrl).play()`` nach
|
||||||
|
// einem await mit "NotAllowedError".
|
||||||
|
let _tourAudio = null;
|
||||||
|
let _tourAudioUnlocked = false;
|
||||||
// Cache nur in dieser Session: vermeidet doppelte API-Roundtrips bei
|
// Cache nur in dieser Session: vermeidet doppelte API-Roundtrips bei
|
||||||
// Vor/Zurück-Navigation. Server-seitig sind die MP3s ohnehin gecacht.
|
// Vor/Zurück-Navigation. Server-seitig sind die MP3s ohnehin gecacht.
|
||||||
const _audioBlobCache = {};
|
const _audioBlobCache = {};
|
||||||
|
|
||||||
|
function ensureAudioElement() {
|
||||||
|
if (!_tourAudio) {
|
||||||
|
_tourAudio = new Audio();
|
||||||
|
_tourAudio.preload = 'auto';
|
||||||
|
}
|
||||||
|
return _tourAudio;
|
||||||
|
}
|
||||||
|
|
||||||
|
function unlockAudioOnGesture() {
|
||||||
|
// Muss SYNCHRON im Click-Handler aufgerufen werden, sonst zählt es
|
||||||
|
// nicht als User-Gesture. Wir starten ein silent-play und pausieren
|
||||||
|
// sofort — das markiert das Element als "vom User initiiert".
|
||||||
|
if (_tourAudioUnlocked) return;
|
||||||
|
const a = ensureAudioElement();
|
||||||
|
try {
|
||||||
|
// 1×1 silent WAV als Data-URL (winzig, garantiert valid)
|
||||||
|
a.src = 'data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAESsAACJWAAACABAAZGF0YQAAAAA=';
|
||||||
|
const p = a.play();
|
||||||
|
if (p && typeof p.then === 'function') {
|
||||||
|
p.then(() => {
|
||||||
|
a.pause();
|
||||||
|
a.currentTime = 0;
|
||||||
|
_tourAudioUnlocked = true;
|
||||||
|
}).catch(() => { /* manche Browser blocken auch das — ignorieren */ });
|
||||||
|
} else {
|
||||||
|
a.pause();
|
||||||
|
_tourAudioUnlocked = true;
|
||||||
|
}
|
||||||
|
} catch (_) { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
function $(id) { return document.getElementById(id); }
|
function $(id) { return document.getElementById(id); }
|
||||||
|
|
||||||
function speakWebSpeech(text) {
|
function speakWebSpeech(text) {
|
||||||
@ -204,8 +238,13 @@
|
|||||||
|
|
||||||
function playAudio(blobUrl) {
|
function playAudio(blobUrl) {
|
||||||
stopSpeak();
|
stopSpeak();
|
||||||
_tourAudio = new Audio(blobUrl);
|
const a = ensureAudioElement();
|
||||||
_tourAudio.play().catch(() => { /* Autoplay-Block, harmless */ });
|
a.src = blobUrl;
|
||||||
|
a.play().catch((err) => {
|
||||||
|
// Wenn der Browser doch blockiert (z.B. Page reload ohne Gesture):
|
||||||
|
// wir loggen, der User kann den Mute-Toggle erneut drücken.
|
||||||
|
console.warn('Tour-Audio play blocked:', err && err.name);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function speak(text) {
|
async function speak(text) {
|
||||||
@ -220,9 +259,10 @@
|
|||||||
try { window.speechSynthesis.cancel(); } catch (_) {}
|
try { window.speechSynthesis.cancel(); } catch (_) {}
|
||||||
}
|
}
|
||||||
_tourUtter = null;
|
_tourUtter = null;
|
||||||
|
// Persistentes _tourAudio nicht zerstören (sonst geht der Unlock
|
||||||
|
// verloren), nur stoppen.
|
||||||
if (_tourAudio) {
|
if (_tourAudio) {
|
||||||
try { _tourAudio.pause(); _tourAudio.currentTime = 0; } catch (_) {}
|
try { _tourAudio.pause(); _tourAudio.currentTime = 0; } catch (_) {}
|
||||||
_tourAudio = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,6 +337,11 @@
|
|||||||
|
|
||||||
// Globale Schnittstelle
|
// Globale Schnittstelle
|
||||||
window.gwoeTourStart = function () {
|
window.gwoeTourStart = function () {
|
||||||
|
// Audio-Element JETZT entsperren — synchron im User-Gesture-Frame.
|
||||||
|
// showStep() macht später async fetches; das play() danach würde
|
||||||
|
// sonst ohne diesen Trick vom Auto-Play-Block kassiert.
|
||||||
|
unlockAudioOnGesture();
|
||||||
|
|
||||||
resolveSteps();
|
resolveSteps();
|
||||||
if (_resolvedSteps.length === 0) {
|
if (_resolvedSteps.length === 0) {
|
||||||
console.warn('Tour: keine sichtbaren Stationen.');
|
console.warn('Tour: keine sichtbaren Stationen.');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user