my_voice_assistant_v3_jamulix/app/web/index.html
dschlueter 37b4c175d2 fix(notruf): ehrlicher Status, wenn niemand verständigt werden kann
Bisher meldete ein Notruf ohne nutzbaren Kanal trotzdem Erfolg
(status="alerted", grüner Haken), obwohl tatsächlich niemand
benachrichtigt wurde — gefährlich für Senioren.

- emergency.py: drei klare Endzustände alerted / no_recipients /
  delivery_failed; notify_readiness() prüft, ob ein Kanal wirklich
  senden würde (SMTP+Empfaenger bzw. Webhook). Mehrsprachige Hinweise
  fuer die Fehlfaelle (nennen Selbsthilfe 112). Antwort traegt jetzt
  notified + notify_ready; Warn-Log, wenn nichts zugestellt wurde.
- /api/emergency/status liefert notify_ready fuer die Vorab-Warnung.
- Frontend: gelbe Vorab-Warnung im SOS-Dialog, wenn kein Empfaenger
  eingerichtet ist; bei no_recipients/delivery_failed kein gruenes
  Erfolgssignal mehr, sondern roter Fehlerzustand (app.js v54).
- Tests an die neue Semantik angepasst.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 16:50:03 +02:00

424 lines
27 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>Alexis</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">Alexis<span class="hidden min-[420px]:inline font-normal text-slate-400 dark:text-slate-500 text-sm">&nbsp;· Ihr Sprachbegleiter</span></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>
<!-- „Neues Gespräch" in der Kopfzeile: ab 384px Viewport sichtbar, darunter
(sehr schmale Phones, z.B. iPhone SE) ausgeblendet, damit der Titel Platz
behält. Das Menü enthält „Neues Gespräch" dauerhaft als Rückfall. -->
<button id="new-chat-top" type="button" title="Neues Gespräch" aria-label="Neues Gespräch"
class="shrink-0 h-9 w-9 hidden min-[384px]:grid place-items-center rounded-lg border border-slate-300 dark:border-slate-600 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" class="w-5 h-5"><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>
</button>
<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>
<div class="flex items-center gap-3">
<span id="app-version" class="opacity-70 tabular-nums"></span>
<a id="logout" class="hidden hover:underline" href="#">Abmelden</a>
</div>
</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>
<p id="sos-confirm-warn"
class="hidden mt-3 rounded-lg bg-amber-100 dark:bg-amber-900/40 px-3 py-2 text-sm font-semibold text-amber-800 dark:text-amber-200">
⚠️ Achtung: Es sind noch keine Notfall-Kontakte eingerichtet. Es kann niemand
automatisch benachrichtigt werden im Notfall bitte selbst 112 anrufen.
</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>
<!-- ═══════════════ Notruf bereits ausgelöst (Sperre) ═══════════════ -->
<div id="sos-cooldown" role="dialog" aria-modal="true" aria-labelledby="sos-cooldown-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-green-600 text-white px-5 py-4 flex items-center gap-3">
<span class="text-3xl font-extrabold"></span>
<span id="sos-cooldown-title" class="text-lg font-semibold">Hilfe ist unterwegs</span>
</div>
<div class="px-5 py-4">
<p id="sos-cooldown-text" class="text-base text-slate-700 dark:text-slate-200"></p>
</div>
<div class="px-5 pb-5">
<button type="button" id="sos-cooldown-ok"
class="w-full h-14 rounded-xl bg-green-600 hover:bg-green-700 text-white text-xl font-extrabold transition-colors">Verstanden</button>
</div>
</div>
</div>
<script src="/app.js?v=54"></script>
</body>
</html>