diff --git a/app/web/app.js b/app/web/app.js
index d3d6ae5..a7ce676 100644
--- a/app/web/app.js
+++ b/app/web/app.js
@@ -60,7 +60,9 @@ let _voices = [];
function loadVoices() { _voices = TTS_SUPPORTED ? speechSynthesis.getVoices() : []; }
if (TTS_SUPPORTED) {
loadVoices();
- speechSynthesis.addEventListener("voiceschanged", loadVoices);
+ // Stimmen laden asynchron -> bei Eintreffen Preset-Zustand neu bewerten (entgrauen).
+ speechSynthesis.addEventListener("voiceschanged", () => { loadVoices(); refreshDevicePreset(); });
+ setTimeout(() => refreshDevicePreset(), 1500); // Backstop, falls kein voiceschanged kommt
}
function pickVoice(bcp) {
@@ -139,13 +141,6 @@ const PLAYBACK_KEY = "va-playback"; // geräte-lokal: "device" wenn Geräte-TTS
// Vorrang: 1. lokale Wahl (localStorage), 2. Server-Pref (tts_provider), 3. Mobil-Default = Gerät.
function applyPlaybackChoice(serverProvider) {
if (!ttsSel) return;
- // "Im Gerät"-Preset ausblenden, wenn der Browser keine Web Speech API hat.
- if (!TTS_SUPPORTED) {
- const opt = ttsSel.querySelector('option[value="device"]');
- if (opt) opt.remove();
- const btn = document.querySelector('.tts-preset[data-tts="device"]');
- if (btn) btn.classList.add("hidden");
- }
const local = localStorage.getItem(PLAYBACK_KEY);
if (local === "device" && TTS_SUPPORTED) {
ttsSel.value = "device";
@@ -157,6 +152,30 @@ function applyPlaybackChoice(serverProvider) {
} else if (!ttsSel.value) {
ttsSel.value = "piper"; // Fallback: Server
}
+ refreshDevicePreset(); // graut „Im Gerät" aus, wenn keine Stimmen + syncPresetUI
+}
+
+// Graut „Im Gerät" aus, wenn keine Sprachstimmen vorhanden sind; entgraut automatisch,
+// sobald welche da sind. Kein Aufzwingen: device wird nur (wieder) gewählt, wenn es die
+// gespeicherte Präferenz ist. Ohne Stimmen NICHT auf der deaktivierten Option sitzen bleiben.
+function refreshDevicePreset() {
+ const btn = document.querySelector('.tts-preset[data-tts="device"]');
+ if (!btn || !ttsSel) return;
+ const ready = deviceVoicesReady();
+ btn.disabled = !ready;
+ btn.classList.toggle("opacity-50", !ready);
+ btn.classList.toggle("cursor-not-allowed", !ready);
+ const sub = btn.querySelector(".tts-sub");
+ if (sub) sub.textContent = ready
+ ? "Das Gerät liest vor · spart Daten"
+ : "keine Sprachstimmen im Browser";
+ if (ready) {
+ if (localStorage.getItem(PLAYBACK_KEY) === "device" && ttsSel.value !== "device") {
+ ttsSel.value = "device"; // gespeicherte Präferenz wieder aktivieren
+ }
+ } else if (ttsSel.value === "device") {
+ ttsSel.value = "piper"; // Server statt deaktivierter Option (Präferenz bleibt)
+ }
syncPresetUI();
}
diff --git a/app/web/index.html b/app/web/index.html
index f0c5a89..fafb5b0 100644
--- a/app/web/index.html
+++ b/app/web/index.html
@@ -261,7 +261,7 @@
class="tts-preset w-full flex items-center gap-3 rounded-lg border border-slate-200 dark:border-slate-700 px-3 py-2 text-left hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-colors">
📱
Im Gerät
- Das Gerät liest vor · spart Daten
+ Das Gerät liest vor · spart Daten
✓