feat(ui): SOS-Button prominent (rot gefüllt, großes weißes „SOS") + Bestätigung & Status
Der Notruf-Button war ein kleines 36px-Outline-Icon (🆘) — leicht zu übersehen.
Jetzt ein rot gefüllter Button (bg-red-600) mit großem, fettem weißem „SOS" in
der (immer sichtbaren) Kopfzeile — der Senior erkennt ihn sofort. Sprachwahl
und Menü bleiben unverändert an ihrem Platz.
- Bestätigung: statt native confirm() ein großer eigener Dialog (rote SOS-Kopf-
zeile, Warnzeile, große Knöpfe „JA, HILFE RUFEN" / „Abbrechen"). Ein Tipp auf
SOS, ein Tipp auf JA — schnell, aber gegen Fehlalarm geschützt.
- Feedback am Button selbst: setSosState idle|calling|done|error —
„Rufe …" (pulsierend) → „Gerufen ✓" (grün) → zurück zu „SOS". So sieht der
Senior unmittelbar am Knopf, dass der Alarm ausgelöst wurde.
Nur Frontend (app/web), live ohne Neustart (No-Cache-Static, Bump auf v=49).
Verifiziert via Headless-Chrome/CDP: Idle/Dialog/Calling/Done-Zustände.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
b4111c887e
commit
0ef4fe9cb8
2 changed files with 92 additions and 26 deletions
|
|
@ -2245,29 +2245,74 @@ function deviceInfo() {
|
|||
return { mobile: /Mobi|Android|iPhone|iPad|iPod/i.test(ua), platform: ua.slice(0, 120) };
|
||||
}
|
||||
|
||||
if (emergencyBtn) {
|
||||
emergencyBtn.addEventListener("click", async () => {
|
||||
if (!confirm("Wirklich Hilfe rufen?\nIch alarmiere sofort Helfer!")) return;
|
||||
const lang = (langSel && langSel.value) || "de";
|
||||
// Sichtbarer Zustand am SOS-Button selbst: idle | calling | done | error.
|
||||
// So erkennt der Senior unmittelbar am Knopf, dass der Alarm ausgelöst wurde.
|
||||
function setSosState(state) {
|
||||
if (!emergencyBtn) return;
|
||||
emergencyBtn.classList.remove(
|
||||
"bg-red-600", "hover:bg-red-700", "bg-green-600", "animate-pulse"
|
||||
);
|
||||
if (state === "calling") {
|
||||
emergencyBtn.textContent = "Rufe …";
|
||||
emergencyBtn.disabled = true;
|
||||
try {
|
||||
const g = await captureGeo();
|
||||
const body = { language: lang, device: deviceInfo() };
|
||||
if (g.ok) body.geo = { lat: g.lat, lon: g.lon, accuracy: g.accuracy, ts: g.ts };
|
||||
else body.geo_error = g.error;
|
||||
const res = await fetch("/api/emergency", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
const notice = (data && data.notice) ||
|
||||
"ACHTUNG: Es ist noch keine Benachrichtigung eingebaut.";
|
||||
addMessage("emergency", "🆘 " + notice);
|
||||
} catch (e) {
|
||||
addMessage("emergency", "🆘 Fehler beim Auslösen des Notrufs.");
|
||||
} finally {
|
||||
emergencyBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
emergencyBtn.classList.add("bg-red-600", "animate-pulse");
|
||||
} else if (state === "done") {
|
||||
emergencyBtn.textContent = "Gerufen ✓";
|
||||
emergencyBtn.disabled = true;
|
||||
emergencyBtn.classList.add("bg-green-600");
|
||||
} else if (state === "error") {
|
||||
emergencyBtn.textContent = "Fehler";
|
||||
emergencyBtn.disabled = false;
|
||||
emergencyBtn.classList.add("bg-red-600", "hover:bg-red-700");
|
||||
} else { // idle
|
||||
emergencyBtn.textContent = "SOS";
|
||||
emergencyBtn.disabled = false;
|
||||
emergencyBtn.classList.add("bg-red-600", "hover:bg-red-700");
|
||||
}
|
||||
}
|
||||
|
||||
async function runEmergency() {
|
||||
const lang = (langSel && langSel.value) || "de";
|
||||
setSosState("calling");
|
||||
try {
|
||||
const g = await captureGeo();
|
||||
const body = { language: lang, device: deviceInfo() };
|
||||
if (g.ok) body.geo = { lat: g.lat, lon: g.lon, accuracy: g.accuracy, ts: g.ts };
|
||||
else body.geo_error = g.error;
|
||||
const res = await fetch("/api/emergency", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
const notice = (data && data.notice) ||
|
||||
"ACHTUNG: Es ist noch keine Benachrichtigung eingebaut.";
|
||||
addMessage("emergency", "🆘 " + notice);
|
||||
setSosState("done");
|
||||
setTimeout(() => setSosState("idle"), 6000); // danach zurück zu „SOS"
|
||||
} catch (e) {
|
||||
addMessage("emergency", "🆘 Fehler beim Auslösen des Notrufs.");
|
||||
setSosState("error");
|
||||
setTimeout(() => setSosState("idle"), 4000);
|
||||
}
|
||||
}
|
||||
|
||||
// Großer eigener Bestätigungsdialog statt native confirm() — seniorentauglich.
|
||||
const sosConfirm = $("#sos-confirm");
|
||||
function openSosConfirm() { if (sosConfirm) sosConfirm.classList.remove("hidden"); }
|
||||
function closeSosConfirm() { if (sosConfirm) sosConfirm.classList.add("hidden"); }
|
||||
|
||||
if (emergencyBtn) {
|
||||
emergencyBtn.addEventListener("click", openSosConfirm);
|
||||
}
|
||||
const sosYesBtn = $("#sos-confirm-yes");
|
||||
const sosNoBtn = $("#sos-confirm-no");
|
||||
if (sosYesBtn) sosYesBtn.addEventListener("click", () => { closeSosConfirm(); runEmergency(); });
|
||||
if (sosNoBtn) sosNoBtn.addEventListener("click", closeSosConfirm);
|
||||
// Klick auf den Hintergrund oder Escape bricht ab (schließt nur den Dialog).
|
||||
if (sosConfirm) {
|
||||
sosConfirm.addEventListener("click", (e) => { if (e.target === sosConfirm) closeSosConfirm(); });
|
||||
}
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Escape" && sosConfirm && !sosConfirm.classList.contains("hidden")) closeSosConfirm();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -229,7 +229,7 @@
|
|||
<img src="/favicon.svg?v=1" alt="" class="w-7 h-7 shrink-0" />
|
||||
<h1 class="flex-1 truncate font-semibold text-base">Voice Assistant</h1>
|
||||
<button type="button" id="emergency-btn" title="Hilfe rufen" aria-label="Hilfe rufen"
|
||||
class="shrink-0 h-9 w-9 grid place-items-center rounded-lg border border-red-300 dark:border-red-800 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors text-base">🆘</button>
|
||||
class="shrink-0 h-11 px-4 grid place-items-center rounded-lg bg-red-600 hover:bg-red-700 text-white font-extrabold tracking-wide text-lg shadow-sm whitespace-nowrap transition-colors disabled:opacity-100">SOS</button>
|
||||
<select id="lang-sel" title="Sprache"
|
||||
class="text-sm rounded-lg border border-slate-300 dark:border-slate-600 bg-transparent px-2 py-1.5">
|
||||
<option value="de">🇩🇪 DE</option>
|
||||
|
|
@ -365,6 +365,27 @@
|
|||
<div id="status" class="w-full max-w-3xl mx-auto mt-1 min-h-[1rem] text-xs text-slate-500 dark:text-slate-400"></div>
|
||||
</footer>
|
||||
|
||||
<script src="/app.js?v=48"></script>
|
||||
<!-- ═══════════════ SOS-Bestätigung (Notruf) ═══════════════ -->
|
||||
<div id="sos-confirm" role="dialog" aria-modal="true" aria-labelledby="sos-confirm-title"
|
||||
class="hidden fixed inset-0 z-[60] flex items-center justify-center bg-black/50 p-4">
|
||||
<div class="w-full max-w-sm rounded-2xl bg-white dark:bg-slate-800 shadow-xl overflow-hidden">
|
||||
<div class="bg-red-600 text-white px-5 py-4 flex items-center gap-3">
|
||||
<span class="text-3xl font-extrabold tracking-wide">SOS</span>
|
||||
<span id="sos-confirm-title" class="text-lg font-semibold">Hilfe rufen?</span>
|
||||
</div>
|
||||
<div class="px-5 py-4">
|
||||
<p class="text-base text-slate-700 dark:text-slate-200">Möchten Sie wirklich Hilfe rufen?</p>
|
||||
<p class="mt-1 text-sm font-semibold text-red-600 dark:text-red-400">Es werden sofort Helfer alarmiert.</p>
|
||||
</div>
|
||||
<div class="px-5 pb-5 grid grid-cols-1 gap-3">
|
||||
<button type="button" id="sos-confirm-yes"
|
||||
class="h-14 rounded-xl bg-red-600 hover:bg-red-700 text-white text-xl font-extrabold tracking-wide transition-colors">JA, HILFE RUFEN</button>
|
||||
<button type="button" id="sos-confirm-no"
|
||||
class="h-12 rounded-xl border border-slate-300 dark:border-slate-600 text-slate-700 dark:text-slate-200 text-lg font-semibold hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors">Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/app.js?v=49"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue