my_voice_assistant_v3_jamulix/app/web/index.html
dschlueter 8a6f428c1b fix(ui): SOS-Button auf Quadrat in Höhe der Sprachauswahl verkleinern (32px)
Der prominente SOS-Button war zu groß. Jetzt ein kompaktes 32x32-Quadrat
(h-8 w-8) — exakt so hoch wie die Sprachauswahl daneben (gemessen 32px),
weiterhin rot gefüllt mit weißem „SOS". Der volle Titel passt wieder ohne
Kürzung.

Da ein 32px-Quadrat keine langen Statuswörter fasst, läuft das Feedback jetzt
über Farbe/Symbol: „SOS" (rot) → pulsiert beim Rufen → „✓" (grün) = Alarm
ausgelöst → „!" bei Fehler, dann zurück. Bestätigungsdialog unverändert.

Bump app.js v=49 -> v=50. Verifiziert via Headless-Chrome (SOS 32x32 == lang 32).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 15:32:42 +02:00

391 lines
24 KiB
HTML

<!doctype html>
<html lang="de" class="h-full">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>Voice Assistant</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg?v=1" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32.png?v=1" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png?v=1" />
<script src="https://cdn.tailwindcss.com"></script>
<script>tailwind.config = { darkMode: "class" };</script>
<style>
/* Dropdown-Popup (<option>) hat sonst Browser-Default-Weiß -> im Dark-Mode
helle Schrift auf hellem Grund = unlesbar. Farben explizit setzen. */
select option { background-color: #ffffff; color: #1e293b; } /* slate-800 */
.dark select option { background-color: #1e293b; color: #f1f5f9; } /* slate-100 */
</style>
<script>
(function () {
try {
var t = localStorage.getItem("theme");
if (t === "dark" || (!t && matchMedia("(prefers-color-scheme: dark)").matches))
document.documentElement.classList.add("dark");
} catch (e) {}
})();
</script>
</head>
<body class="h-[100dvh] flex flex-col bg-slate-50 text-slate-800 dark:bg-slate-900 dark:text-slate-100">
<!-- ═══════════════════ ADMIN PANEL OVERLAY ═══════════════════ -->
<div id="admin-panel" class="hidden fixed inset-0 z-50 flex flex-col bg-slate-50 dark:bg-slate-900">
<!-- Admin Header -->
<header class="shrink-0 h-14 flex items-center gap-3 px-4 border-b border-slate-200 dark:border-slate-700 bg-white/90 dark:bg-slate-800/90 backdrop-blur">
<span class="text-lg">⚙️</span>
<h2 class="flex-1 font-semibold text-base">Admin-Bereich</h2>
<button id="admin-close" title="Schließen"
class="h-9 w-9 grid place-items-center rounded-lg text-slate-500 hover:bg-slate-100 dark:hover:bg-slate-700 text-xl leading-none transition-colors"></button>
</header>
<!-- Tab Navigation (5 logische Bereiche) -->
<nav class="shrink-0 flex border-b border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 overflow-x-auto">
<button class="admin-tab px-4 py-3 text-sm font-medium whitespace-nowrap border-b-2 border-transparent text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200 transition-colors" data-tab="overview">📊 Übersicht</button>
<button class="admin-tab px-4 py-3 text-sm font-medium whitespace-nowrap border-b-2 border-transparent text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200 transition-colors" data-tab="users">👥 Nutzer</button>
<button class="admin-tab px-4 py-3 text-sm font-medium whitespace-nowrap border-b-2 border-transparent text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200 transition-colors" data-tab="system">🖥 System</button>
<button class="admin-tab px-4 py-3 text-sm font-medium whitespace-nowrap border-b-2 border-transparent text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200 transition-colors" data-tab="config">⚙ Konfiguration</button>
</nav>
<!-- Sub-Navigation (dynamisch, nur bei mehreren Abschnitten) -->
<div id="admin-subnav" class="hidden shrink-0 flex gap-1.5 px-3 py-2 border-b border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800/40 overflow-x-auto"></div>
<!-- Tab Content Container -->
<div class="flex-1 overflow-hidden">
<!-- ── Übersicht (Dashboard) ── -->
<div id="admin-tab-overview" class="hidden h-full overflow-y-auto p-4">
<div class="max-w-3xl mx-auto">
<div class="flex items-center justify-between mb-4">
<h3 class="font-semibold text-base">Übersicht</h3>
<button id="load-overview"
class="rounded-lg border border-slate-300 dark:border-slate-600 text-sm px-3 py-1.5 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors">Aktualisieren</button>
</div>
<div id="overview-content" class="space-y-4">
<p class="text-sm text-slate-400">lade …</p>
</div>
</div>
</div>
<!-- ── Tab: Nutzer ── -->
<div id="admin-tab-users" class="h-full overflow-y-auto p-4">
<div class="max-w-3xl mx-auto space-y-4">
<!-- Nutzer anlegen -->
<div class="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-4">
<h3 class="text-sm font-medium text-slate-700 dark:text-slate-300 mb-3">Nutzer anlegen</h3>
<form id="create-user-form" class="flex gap-2">
<input id="new-user-name" type="text" placeholder="Anzeigename" required
class="flex-1 rounded-lg border border-slate-300 dark:border-slate-600 bg-transparent px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
<button type="submit"
class="rounded-lg bg-blue-600 hover:bg-blue-700 text-white text-sm px-4 py-2 font-medium transition-colors">Anlegen</button>
</form>
<div id="new-token-box" class="hidden mt-3 rounded-lg bg-emerald-50 dark:bg-emerald-900/30 border border-emerald-200 dark:border-emerald-800 p-3">
<p class="text-xs text-emerald-700 dark:text-emerald-400 mb-1 font-medium">Login-Link (nur einmal sichtbar — jetzt kopieren &amp; an den Nutzer geben!):</p>
<code id="new-token-value" class="text-xs font-mono break-all text-emerald-800 dark:text-emerald-200 select-all"></code>
</div>
</div>
<!-- Nutzerliste -->
<div id="admin-user-cards" class="space-y-3">
<p class="text-sm text-slate-400 text-center py-4">lade …</p>
</div>
</div>
</div>
<!-- ── Tab: Gespräche ── -->
<div id="admin-tab-chat" class="hidden h-full flex overflow-hidden">
<!-- Linke Spalte: Nutzerliste -->
<div class="w-48 md:w-60 shrink-0 border-r border-slate-200 dark:border-slate-700 flex flex-col">
<div class="px-3 py-2.5 border-b border-slate-200 dark:border-slate-700 bg-slate-100/60 dark:bg-slate-800/60">
<p class="text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wide">Nutzer</p>
</div>
<ul id="chat-user-list" class="flex-1 overflow-y-auto divide-y divide-slate-100 dark:divide-slate-800/60"></ul>
</div>
<!-- Rechte Spalte: Sessions & Transkript -->
<div id="chat-right-panel" class="flex-1 overflow-y-auto">
<p class="p-6 text-sm text-slate-400">← Nutzer auswählen</p>
</div>
</div>
<!-- ── Tab: Notfälle ── -->
<div id="admin-tab-emergency" class="hidden h-full overflow-y-auto p-4">
<div class="max-w-4xl mx-auto">
<div class="flex items-center justify-between mb-4">
<h3 class="font-semibold text-base">Notrufe (vom Nutzer ausgelöst)</h3>
<button id="load-emergency"
class="rounded-lg border border-slate-300 dark:border-slate-600 text-sm px-3 py-1.5 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors">Aktualisieren</button>
</div>
<div id="emergency-content">
<p class="text-sm text-slate-400">lade …</p>
</div>
</div>
</div>
<!-- ── Tab: Status ── -->
<div id="admin-tab-status" class="hidden h-full overflow-y-auto p-4">
<div class="max-w-3xl mx-auto">
<div class="flex items-center justify-between mb-4">
<h3 class="font-semibold text-base">System-Status</h3>
<button id="load-status"
class="rounded-lg border border-slate-300 dark:border-slate-600 text-sm px-3 py-1.5 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors">Aktualisieren</button>
</div>
<div id="status-content" class="space-y-4">
<p class="text-sm text-slate-400">lade …</p>
</div>
</div>
</div>
<!-- ── Tab: Metriken ── -->
<div id="admin-tab-metrics" class="hidden h-full overflow-y-auto p-4">
<div class="max-w-3xl mx-auto">
<div class="flex items-center justify-between mb-4">
<h3 class="font-semibold text-base">Nutzungsstatistik</h3>
<button id="load-metrics"
class="rounded-lg border border-slate-300 dark:border-slate-600 text-sm px-3 py-1.5 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors">Aktualisieren</button>
</div>
<div id="metrics-content">
<p class="text-sm text-slate-400">lade …</p>
</div>
</div>
</div>
<!-- ── Tab: Wörterbuch ── -->
<div id="admin-tab-words" class="hidden h-full overflow-y-auto p-4">
<div class="max-w-3xl mx-auto">
<div class="flex items-center justify-between mb-4 flex-wrap gap-2">
<h3 class="font-semibold text-base">Aussprache-Lexikon</h3>
<div class="flex items-center gap-2">
<select id="words-lang"
class="text-sm rounded-lg border border-slate-300 dark:border-slate-600 bg-transparent px-2 py-1.5">
<option value="de">Deutsch (de)</option>
<option value="en">Englisch (en)</option>
<option value="fr">Französisch (fr)</option>
<option value="es">Spanisch (es)</option>
<option value="it">Italienisch (it)</option>
<option value="pt">Portugiesisch (pt)</option>
<option value="pl">Polnisch (pl)</option>
<option value="ar">Arabisch (ar)</option>
<option value="ru">Russisch (ru)</option>
<option value="zh">Chinesisch (zh)</option>
</select>
<button id="load-words"
class="rounded-lg border border-slate-300 dark:border-slate-600 text-sm px-3 py-1.5 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors">Aktualisieren</button>
</div>
</div>
<p class="text-xs text-slate-400 mb-4">Wirkt nur bei lokalem TTS (Piper, Stufe „full"). <strong>Abkürzungen</strong> ersetzen ganze Token. <strong>Einheiten</strong> wirken nur direkt nach einer Zahl. <strong>Begriffe</strong> für Eigennamen, Fachwörter, Aussprache-Korrekturen.</p>
<div id="words-content" class="space-y-6">
<p class="text-sm text-slate-400">lade …</p>
</div>
</div>
</div>
<!-- ── Tab: Wartung ── -->
<div id="admin-tab-maintenance" class="hidden h-full overflow-y-auto p-4">
<div class="max-w-3xl mx-auto">
<h3 class="font-semibold text-base mb-4">Wartung</h3>
<div id="maintenance-content" class="space-y-4">
<p class="text-sm text-slate-400">lade …</p>
</div>
</div>
</div>
<!-- ── Tab: Log ── -->
<div id="admin-tab-log" class="hidden h-full flex flex-col">
<div class="shrink-0 flex items-center gap-3 px-4 py-2.5 border-b border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800/60">
<button id="log-connect"
class="rounded-lg bg-emerald-600 hover:bg-emerald-700 text-white text-xs px-3 py-1.5 font-medium transition-colors">▶ Verbinden</button>
<button id="log-disconnect" disabled
class="rounded-lg border border-slate-300 dark:border-slate-600 text-xs px-3 py-1.5 opacity-40 transition-colors">■ Trennen</button>
<span id="log-status" class="text-xs text-slate-400">getrennt</span>
<button id="log-clear" class="ml-auto text-xs text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 transition-colors">Leeren</button>
</div>
<div id="log-output"
class="flex-1 overflow-y-auto bg-slate-950 text-emerald-300 font-mono text-xs p-4 leading-relaxed whitespace-pre-wrap">
<span class="text-slate-500">— Verbinden um Log zu starten —</span>
</div>
</div>
<!-- ── Tab: Einstellungen ── -->
<div id="admin-tab-settings" class="hidden h-full overflow-y-auto p-4">
<div class="max-w-3xl mx-auto">
<div class="flex items-center justify-between mb-2 flex-wrap gap-2">
<h3 class="font-semibold text-base">Laufzeit-Einstellungen</h3>
<button id="load-settings"
class="rounded-lg border border-slate-300 dark:border-slate-600 text-sm px-3 py-1.5 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors">Aktualisieren</button>
</div>
<p class="text-xs text-slate-400 mb-4">Änderungen wirken sofort — kein Server-Neustart nötig. „Zurücksetzen" entfernt den Override und der .env-Wert gilt wieder.</p>
<div id="settings-content" class="space-y-2">
<p class="text-sm text-slate-400">lade …</p>
</div>
</div>
</div>
</div><!-- /Tab Content Container -->
</div><!-- /Admin Panel -->
<!-- ═══════════════════ CHAT INTERFACE ═══════════════════ -->
<header class="shrink-0 h-14 flex items-center gap-2 px-3 border-b border-slate-200 dark:border-slate-700 bg-white/80 dark:bg-slate-800/80 backdrop-blur">
<img src="/favicon.svg?v=1" alt="" class="w-7 h-7 shrink-0" />
<h1 class="flex-1 truncate font-semibold text-base">Voice&nbsp;Assistant</h1>
<button type="button" id="emergency-btn" title="Hilfe rufen" aria-label="Hilfe rufen"
class="shrink-0 h-8 w-8 grid place-items-center rounded-lg bg-red-600 hover:bg-red-700 text-white font-extrabold text-sm leading-none shadow-sm 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>
<option value="en">🇬🇧 EN</option>
<option value="fr">🇫🇷 FR</option>
<option value="es">🇪🇸 ES</option>
<option value="it">🇮🇹 IT</option>
<option value="pt">🇧🇷 PT</option>
<option value="pl">🇵🇱 PL</option>
<option value="ar">🇸🇦 AR</option>
<option value="ru">🇷🇺 RU</option>
<option value="zh">🇨🇳 ZH</option>
</select>
<button id="menu-toggle" title="Menü" aria-label="Menü"
class="h-9 w-9 grid place-items-center rounded-lg border border-slate-300 dark:border-slate-600 hover:bg-slate-100 dark:hover:bg-slate-700 text-xl leading-none transition-colors"></button>
</header>
<!-- Identitätsleiste: angemeldeter Nutzer (links) + Abmelden (rechts) -->
<div class="shrink-0 flex items-center justify-between px-4 py-1 text-xs text-slate-500 dark:text-slate-400 border-b border-slate-200 dark:border-slate-700">
<span id="identity">lade …</span>
<a id="logout" class="hidden hover:underline" href="#">Abmelden</a>
</div>
<!-- Verstecktes Quell-Select: speist die Ton-Presets (bewahrt die bestehende Logik) -->
<select id="tts" class="hidden">
<option value="device">Im Gerät</option>
<option value="piper">Server</option>
<option value="chatterbox">Beste Qualität</option>
<option value="cartesia">Cartesia</option>
<option value="openrouter">Cloud</option>
</select>
<!-- ═══════════════════ EINSTELLUNGS-SHEET ═══════════════════ -->
<div id="settings-backdrop" class="hidden fixed inset-0 z-40 bg-black/30"></div>
<div id="settings-sheet"
class="hidden fixed top-14 right-2 z-50 w-[min(20rem,calc(100vw-1rem))] max-h-[80vh] overflow-y-auto
rounded-xl border border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 shadow-xl p-3 space-y-3">
<!-- Vorlesen (TTS-Presets) -->
<div>
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400 mb-1.5 px-1">Vorlesen</p>
<div class="space-y-1.5">
<button type="button" data-tts="device"
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">
<span class="text-lg shrink-0">📱</span>
<span class="flex-1 min-w-0"><span class="block text-sm font-medium">Im Gerät</span>
<span class="tts-sub block text-xs text-slate-500 dark:text-slate-400">Das Gerät liest vor · spart Daten</span></span>
<span class="check text-blue-600 dark:text-blue-400 font-bold opacity-0"></span>
</button>
<button type="button" data-tts="piper"
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">
<span class="text-lg shrink-0"></span>
<span class="flex-1 min-w-0"><span class="block text-sm font-medium">Schnell</span>
<span class="block text-xs text-slate-500 dark:text-slate-400">Piper · lokal, zuverlässig</span></span>
<span class="check text-blue-600 dark:text-blue-400 font-bold opacity-0"></span>
</button>
<button type="button" data-tts="chatterbox"
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">
<span class="text-lg shrink-0"></span>
<span class="flex-1 min-w-0"><span class="block text-sm font-medium">Hohe Qualität</span>
<span class="block text-xs text-slate-500 dark:text-slate-400">Chatterbox · natürliche Stimme</span></span>
<span class="check text-blue-600 dark:text-blue-400 font-bold opacity-0"></span>
</button>
<button type="button" data-tts="cartesia"
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">
<span class="text-lg shrink-0">🎙</span>
<span class="flex-1 min-w-0"><span class="block text-sm font-medium">Cartesia</span>
<span class="tts-sub block text-xs text-slate-500 dark:text-slate-400">Cartesia Sonic · natürlich &amp; schnell</span></span>
<span class="check text-blue-600 dark:text-blue-400 font-bold opacity-0"></span>
</button>
<button type="button" data-tts="openrouter"
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">
<span class="text-lg shrink-0"></span>
<span class="flex-1 min-w-0"><span class="block text-sm font-medium">Cloud</span>
<span class="block text-xs text-slate-500 dark:text-slate-400">OpenRouter · benötigt Internet</span></span>
<span class="check text-blue-600 dark:text-blue-400 font-bold opacity-0"></span>
</button>
</div>
<p id="cartesia-lang-hint" class="hidden mt-2 px-1 text-xs text-amber-600 dark:text-amber-400"></p>
</div>
<!-- Geschlechtspräferenz (wirkt bei Cartesia) -->
<div id="gender-pref-row">
<p class="text-xs font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400 mb-1.5 px-1">Stimme</p>
<div class="flex gap-2">
<button type="button" id="gender-m"
class="gender-btn flex-1 rounded-lg border border-slate-200 dark:border-slate-700 py-1.5 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50 dark:hover:bg-slate-700/50">
♂ Männlich
</button>
<button type="button" id="gender-f"
class="gender-btn flex-1 rounded-lg border border-slate-200 dark:border-slate-700 py-1.5 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50 dark:hover:bg-slate-700/50">
♀ Weiblich
</button>
</div>
</div>
<!-- Aktionen -->
<div class="border-t border-slate-100 dark:border-slate-700 pt-2 space-y-1">
<button id="new-chat" type="button"
class="w-full flex items-center gap-3 rounded-lg px-3 py-2 text-sm hover:bg-slate-50 dark:hover:bg-slate-700/50 text-left transition-colors">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" class="w-5 h-5 shrink-0"><path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"/></svg>
Neues Gespräch
</button>
<button id="theme-toggle" type="button"
class="w-full flex items-center gap-3 rounded-lg px-3 py-2 text-sm hover:bg-slate-50 dark:hover:bg-slate-700/50 text-left transition-colors">
<span id="theme-icon" class="w-5 text-center shrink-0">🌙</span>
<span>Tag-/Nachtmodus</span>
</button>
<button id="admin-toggle" type="button"
class="hidden w-full flex items-center gap-3 rounded-lg px-3 py-2 text-sm hover:bg-slate-50 dark:hover:bg-slate-700/50 text-left transition-colors">
<span class="w-5 text-center shrink-0"></span>
<span>Admin-Bereich</span>
</button>
</div>
</div>
<main id="messages" class="flex-1 overflow-y-auto px-4 py-4 space-y-3 w-full max-w-3xl mx-auto"></main>
<footer class="shrink-0 border-t border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 px-3 pt-3 pb-[calc(env(safe-area-inset-bottom)+0.6rem)]">
<form id="prompt-form" class="flex items-center gap-2 w-full max-w-3xl mx-auto">
<button type="button" id="mic" title="Mikrofon" aria-label="Mikrofon"
class="shrink-0 h-11 w-11 grid place-items-center rounded-full bg-emerald-600 text-white text-lg">🎤</button>
<input id="prompt" type="text" autocomplete="off" dir="auto" placeholder="Nachricht …"
class="flex-1 min-w-0 rounded-xl border border-slate-300 dark:border-slate-600 bg-transparent px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-blue-500" />
<button type="submit" id="send" title="Senden" aria-label="Senden"
class="shrink-0 h-11 w-11 grid place-items-center rounded-full bg-blue-600 hover:bg-blue-700 text-white transition-colors">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 12 3.27 4.36a.6.6 0 0 1 .82-.74l16.2 7.83a.6.6 0 0 1 0 1.08l-16.2 7.83a.6.6 0 0 1-.82-.74L6 12Zm0 0h7" />
</svg>
</button>
</form>
<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>
<!-- ═══════════════ 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=50"></script>
</body>
</html>