<?php
require __DIR__ . '/../config.php';
require FUNCTIONS_ROOT . '/functions_db.php';
require FUNCTIONS_ROOT . '/functions_wecker.php';
require FUNCTIONS_ROOT . '/functions_common.php';

$wecker = function_wecker_hole_alle();
$platzhalter_system = function_tts_hole_aktive_platzhalter();
$platzhalter_custom = function_tts_custom_hole_alle();
$tts_models = function_tts_models_laden();
$wochentage_labels = function_wochentage_labels();
$wecker_fuer_js = function_wecker_fuer_frontend($wecker);
$zeitzonen = timezone_identifiers_list();
$sleep_mode_sekunden = (int)(function_einstellung_hole('sleep_mode_sekunden') ?? 0);
sort($zeitzonen);
?>
<!DOCTYPE html>
<html lang="de" id="htmlRoot">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="theme-color" content="#ffffff" id="metaThemeColor">
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="Wecker">
<title>Wecker</title>
<link rel="manifest" href="/manifest.json">
<link rel="apple-touch-icon" href="/icon.php?name=icon.svg">
<script>
(function(){
    var t = localStorage.getItem('wecker-theme');
    if (t !== 'light' && t !== 'dark') {
        t = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
    }
    document.documentElement.setAttribute('data-theme', t);
    var m = document.getElementById('metaThemeColor');
    if (m) m.content = t === 'dark' ? '#0d1117' : '#ffffff';
})();
</script>
<style>
:root, [data-theme="light"] {
    --bg: #ffffff;
    --card: #ffffff;
    --card-border: #eaeaea;
    --card-hover: #f5f5f5;
    --input-bg: #f5f5f5;
    --text: #1a1a1a;
    --text-muted: #6b6b6b;
    --text-inactive: #b0b0b0;
    --accent: #2563eb;
    --toggle-on: #7c3aed;
    --danger: #dc2626;
    --safe-top: env(safe-area-inset-top, 0);
    --safe-bottom: env(safe-area-inset-bottom, 0);
    --touch: 48px;
}

[data-theme="dark"] {
    --bg: #0d1117;
    --card: #161b22;
    --card-border: #30363d;
    --card-hover: #21262d;
    --input-bg: #0d1117;
    --text: #e6edf3;
    --text-muted: #8b949e;
    --text-inactive: #6e7681;
    --accent: #58a6ff;
    --toggle-on: #7c3aed;
    --danger: #f85149;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

/* Ladebildschirm */
#loadingScreen {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: var(--bg);
    transition: opacity 0.35s ease, visibility 0.35s ease;
}
#loadingScreen.hidden {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
}
#loadingScreen .spinner {
    width: 48px;
    height: 48px;
    border: 4px solid var(--card-border);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: loadingSpin 0.9s linear infinite;
}
#loadingScreen .text {
    margin-top: 16px;
    font-size: 1rem;
    color: var(--text-muted);
}
@keyframes loadingSpin {
    to { transform: rotate(360deg); }
}

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    min-height: 100vh;
    min-height: 100dvh;
    padding-bottom: calc(56px + var(--safe-bottom));
}

/* Header – „Alarm in X Minuten“ + Datum */
header {
    padding: 20px 16px;
    padding-top: max(20px, var(--safe-top));
    background: var(--bg);
    border-bottom: 1px solid var(--card-border);
    position: relative;
}

.header-top {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
}

.header-title {
    font-size: clamp(1.5rem, 6vw, 1.75rem);
    font-weight: 700;
    color: var(--text);
    margin: 0;
    line-height: 1.2;
}

.header-subtitle {
    font-size: 0.9375rem;
    color: var(--text-muted);
    margin: 4px 0 0 0;
}
.header-subtitle-empty { opacity: 0; pointer-events: none; }

.header-actions {
    display: flex;
    gap: 4px;
}

.header-btn {
    min-width: 44px;
    min-height: 44px;
    padding: 0;
    background: transparent;
    border: none;
    color: var(--text-muted);
    font-size: 1.5rem;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 8px;
}
.header-btn:hover { color: var(--text); background: var(--card-hover); }
.header-btn:active { transform: scale(0.96); }
.header-btn.danger { color: var(--danger); }
.header-btn .theme-icon { width: 22px; height: 22px; display: block; }
[data-theme="dark"] .header-btn .theme-icon { filter: invert(1); }
.header-btn img.theme-icon { vertical-align: middle; }

/* Alarm-Liste */
.main {
    padding: 12px 16px;
    max-width: 600px;
    margin: 0 auto;
}

.wecker {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 14px 0;
    border-bottom: 1px solid var(--card-border);
    cursor: pointer;
    transition: background 0.15s;
    min-height: 72px;
}
.wecker:hover { background: var(--card-hover); }
.wecker:last-child { border-bottom: none; }

.wecker-left { flex: 1; min-width: 0; }
.wecker-label {
    font-size: 0.875rem;
    color: var(--text);
    margin: 0 0 2px 0;
    display: block;
}
.wecker.inaktiv .wecker-label { color: var(--text-inactive); }
.weckzeit {
    font-size: 1.75rem;
    font-weight: 400;
    letter-spacing: 0.02em;
    color: var(--text);
}
.wecker.inaktiv .weckzeit { color: var(--text-inactive); }

.wecker-meta {
    font-size: 0.8125rem;
    color: var(--text-muted);
    margin-top: 2px;
}
.wecker.inaktiv .wecker-meta { color: var(--text-inactive); }

.toggle {
    flex-shrink: 0;
}
.toggle input { display: none; }
.toggle span {
    display: block;
    width: 51px;
    height: 31px;
    background: #d1d5db;
    border-radius: 31px;
    position: relative;
    transition: background 0.2s;
}
.toggle span::after {
    content: '';
    position: absolute;
    width: 27px;
    height: 27px;
    background: #fff;
    border-radius: 50%;
    top: 2px;
    left: 2px;
    transition: transform 0.2s;
    box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}
.toggle input:checked + span { background: var(--accent); }
.toggle input:checked + span::after { transform: translateX(20px); }

/* Panels */
.panel { display: none; padding-bottom: calc(56px + var(--safe-bottom)); }
.panel.active { display: block; }
.panel .panel-inner { padding: 12px 16px; max-width: 600px; margin: 0 auto; }
.panel .panel-header { padding: 20px 16px; padding-top: max(20px, var(--safe-top)); background: var(--bg); border-bottom: 1px solid var(--card-border); }
.panel h2.panel-heading { font-size: 1.1rem; margin: 0 0 12px 0; color: var(--text-muted); }

.timer-inner { text-align: center; padding-top: 24px; }
.timer-set { display: flex; align-items: center; justify-content: center; gap: 8px; margin-bottom: 16px; }
.timer-set input { width: 64px; padding: 10px; text-align: center; font-size: 1.25rem; background: var(--input-bg); border: 1px solid var(--card-border); border-radius: 8px; color: var(--text); }
.timer-presets { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; margin-bottom: 20px; }
.timer-preset-btn { min-width: 64px; padding: 10px 14px; background: var(--card-hover); border: 1px solid var(--card-border); border-radius: 10px; color: var(--text); font-size: 0.95rem; cursor: pointer; transition: background 0.2s; }
.timer-preset-btn:hover { background: var(--card); }
.timer-preset-btn:active { transform: scale(0.98); }
.timer-anzeige { font-size: 2.5rem; font-weight: 700; letter-spacing: 0.05em; color: var(--text); margin-bottom: 16px; font-variant-numeric: tabular-nums; }
.timer-buttons { display: flex; gap: 10px; justify-content: center; }
#timerPresetEditOverlay { position: fixed; inset: 0; background: rgba(0,0,0,0.4); z-index: 100; display: none; align-items: center; justify-content: center; }
#timerPresetEditOverlay.show { display: flex; }
#timerPresetEditBox { background: var(--card); border: 1px solid var(--card-border); border-radius: 12px; padding: 20px; min-width: 260px; }
#timerPresetEditBox h3 { margin: 0 0 12px 0; font-size: 1rem; color: var(--accent); }
.timer-preset-edit-actions { display: flex; gap: 10px; margin-top: 14px; justify-content: flex-end; }
.timer-preset-edit-actions .header-btn { min-height: 40px; padding: 8px 16px; }
.timer-preset-edit-input { width: 100%; padding: 10px 12px; font-size: 1rem; background: var(--input-bg); border: 1px solid var(--card-border); border-radius: 8px; color: var(--text); box-sizing: border-box; }
.timer-preset-edit-input:focus { outline: none; border-color: var(--accent); }
.timer-preset-edit-label { display: block; margin-bottom: 6px; font-size: 0.9rem; color: var(--text-muted); }

.musik-inner { padding: 16px; }
.musik-upload { margin-bottom: 20px; display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.musik-upload-label { font-size: 0.9rem; color: var(--text-muted); display: block; width: 100%; margin-bottom: 4px; }
.musik-upload-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; width: 100%; }
.musik-album-label { font-size: 0.85rem; color: var(--text-muted); }
.musik-album-input { flex: 1; min-width: 120px; padding: 8px 10px; background: var(--input-bg); border: 1px solid var(--card-border); border-radius: 8px; color: var(--text); font-size: 0.9rem; }
.musik-album-select { min-width: 140px; padding: 8px 10px; background: var(--input-bg); border: 1px solid var(--card-border); border-radius: 8px; color: var(--text); font-size: 0.9rem; }
.musik-alben-section { margin-bottom: 20px; }
.musik-alben-create { display: flex; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; }
.musik-alben-liste { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.musik-alben-liste li { display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; background: var(--input-bg); border: 1px solid var(--card-border); border-radius: 8px; }
.musik-alben-liste li span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.musik-alben-liste li button { flex-shrink: 0; padding: 4px 10px; font-size: 0.85rem; }
.musik-alben-liste li .album-cover-wrap { width: 40px; height: 40px; border-radius: 8px; overflow: hidden; background: var(--card-hover); flex-shrink: 0; }
.musik-alben-liste li .album-cover-wrap img { width: 100%; height: 100%; object-fit: cover; display: block; }
.album-edit-icon-row { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
.album-edit-cover { width: 48px; height: 48px; flex-shrink: 0; }
.album-edit-icon-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.musik-upload-status { font-size: 0.85rem; color: var(--text-muted); }
.musik-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.musik-info .musik-titel { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.musik-info .musik-album { font-size: 0.8rem; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.musik-playlist-checkboxes, .musik-edit-playlists { display: flex; flex-wrap: wrap; gap: 10px 16px; margin-bottom: 8px; }
.musik-playlist-checkboxes label, .musik-edit-playlists label { display: flex; align-items: center; gap: 6px; font-size: 0.9rem; color: var(--text); cursor: pointer; }
.musik-playlist-checkboxes input, .musik-edit-playlists input { flex-shrink: 0; }
.musik-list-wrap { margin-top: 16px; }
.musik-list-title { font-size: 0.95rem; color: var(--accent); margin: 0 0 10px 0; }
.musik-liste { list-style: none; margin: 0; padding: 0; }
.musik-liste li {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    padding: 12px 14px; margin-bottom: 8px; background: var(--input-bg); border: 1px solid var(--card-border);
    border-radius: 10px; font-size: 0.9rem; color: var(--text);
}
.musik-liste li .musik-cover-wrap { flex-shrink: 0; width: 48px; height: 48px; border-radius: 8px; overflow: hidden; background: var(--card-hover); }
.musik-liste li .musik-cover { width: 100%; height: 100%; object-fit: cover; display: block; }
.musik-liste li .musik-size { font-size: 0.8rem; color: var(--text-muted); flex-shrink: 0; }
.musik-liste li button { flex-shrink: 0; padding: 6px 12px; font-size: 0.85rem; }
.musik-edit-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 200; display: none; align-items: center; justify-content: center; padding: 20px; }
.musik-edit-overlay[aria-hidden="false"] { display: flex; }
.musik-edit-box { background: var(--card-bg); border: 1px solid var(--card-border); border-radius: 12px; padding: 20px; max-width: 360px; width: 100%; }
.musik-edit-title { margin: 0 0 14px 0; font-size: 1rem; color: var(--text); }
.musik-edit-label { display: block; font-size: 0.85rem; color: var(--text-muted); margin-bottom: 4px; }
.musik-edit-input { width: 100%; padding: 8px 10px; margin-bottom: 12px; background: var(--input-bg); border: 1px solid var(--card-border); border-radius: 8px; color: var(--text); box-sizing: border-box; }
.musik-edit-actions { display: flex; gap: 10px; justify-content: flex-end; margin-top: 16px; }

.wetter-suche { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; align-items: center; }
.wetter-suche input { flex: 1; min-width: 100px; padding: 10px 12px; background: var(--input-bg); border: 1px solid var(--card-border); border-radius: 8px; color: var(--text); }
.wetter-ort-anzeige { font-size: 0.95rem; color: var(--text-muted); flex: 1; min-width: 0; }
.wetter-ergebnis { font-size: 0.95rem; color: var(--text); }
.wetter-ergebnis .wetter-aktuell { font-size: 1.25rem; margin-bottom: 12px; }
.wetter-ergebnis .wetter-vorhersage { margin-top: 12px; }
.wetter-ergebnis .tag { padding: 8px 0; border-bottom: 1px solid var(--card-border); }

/* Bottom Navigation */
.update-toast {
    position: fixed;
    top: max(16px, var(--safe-top, 0));
    right: max(16px, var(--safe-right, 0));
    z-index: 100;
    max-width: 280px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.2);
}
.update-toast-inner {
    background: var(--card-bg);
    border: 1px solid var(--card-border);
    border-radius: 12px;
    padding: 12px 14px;
}
.update-toast-text { margin: 0 0 10px 0; font-size: 0.95rem; font-weight: 500; color: var(--text); }
.update-toast-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.update-toast-close { min-width: 36px; padding: 6px 10px; font-size: 1.2rem; line-height: 1; }

.bottom-nav {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    height: calc(56px + var(--safe-bottom));
    padding-bottom: var(--safe-bottom);
    background: var(--bg);
    border-top: 1px solid var(--card-border);
    display: flex;
    justify-content: space-around;
    align-items: center;
    z-index: 20;
}

.bottom-nav a, .bottom-nav span, .bottom-nav button {
    font-size: 0.75rem;
    color: var(--text-muted);
    text-decoration: none;
    padding: 8px 6px;
    border: none;
    background: none;
    cursor: pointer;
    border-bottom: 2px solid transparent;
    font-family: inherit;
}
.bottom-nav a:hover, .bottom-nav button:hover { color: var(--text); }
.bottom-nav a.active, .bottom-nav span.active, .bottom-nav button.active {
    color: var(--accent);
    border-bottom-color: var(--accent);
    font-weight: 500;
}

/* Bildschirmtastatur (Touch) – über Overlays (Einstellungen, Bearbeiten) */
.touch-keyboard {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 150;
    padding: 8px 6px calc(12px + var(--safe-bottom));
    background: var(--card);
    border-top: 1px solid var(--card-border);
    box-shadow: 0 -4px 20px rgba(0,0,0,0.15);
    display: none;
    flex-direction: column;
    gap: 6px;
    max-height: 42vh;
}
.touch-keyboard.visible { display: flex; }
.touch-keyboard-row {
    display: flex;
    justify-content: center;
    gap: 4px;
    flex-wrap: nowrap;
}
.touch-keyboard-key {
    min-width: 28px;
    min-height: 44px;
    padding: 0 6px;
    font-size: 1rem;
    font-family: inherit;
    color: var(--text);
    background: var(--input-bg);
    border: 1px solid var(--card-border);
    border-radius: 8px;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    touch-action: manipulation;
    flex: 0 0 auto;
}
.touch-keyboard-key:active { background: var(--card-hover); }
.touch-keyboard-key--wide { flex: 1; max-width: 120px; }
.touch-keyboard-key--space { flex: 2; min-width: 80px; max-width: 200px; }
.touch-keyboard-key--action { background: var(--card-hover); font-size: 0.85rem; }
.touch-keyboard-key--shift.active { background: var(--accent); color: var(--bg); border-color: var(--accent); }

/* Overlays */
.overlay {
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.5);
    display: none;
    align-items: center;
    justify-content: center;
    z-index: 100;
    padding: var(--safe-top) 0 var(--safe-bottom);
    overflow-y: auto;
}

.overlay-box {
    background: var(--bg);
    border: 1px solid var(--card-border);
    padding: 20px;
    border-radius: 16px;
    width: 94%;
    max-width: 440px;
    max-height: 85vh;
    max-height: 85dvh;
    overflow-y: auto;
    margin: auto;
    box-shadow: 0 4px 24px rgba(0,0,0,0.12);
}

.overlay-box h2 { margin-top: 0; margin-bottom: 18px; font-size: 1.25rem; color: var(--text); }

.form-row { margin-bottom: 14px; }
.form-row label { display: block; margin-bottom: 6px; color: var(--text-muted); font-size: 0.875rem; }
.form-row input[type="time"],
.form-row input[type="date"],
.form-row input[type="text"],
.form-row select,
.form-row textarea {
    width: 100%;
    min-height: var(--touch);
    padding: 12px 14px;
    background: var(--input-bg);
    border: 1px solid var(--card-border);
    border-radius: 12px;
    color: var(--text);
    font-size: 1rem;
}
.form-row textarea { min-height: 88px; resize: vertical; }
.platzhalter-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.platzhalter-chip {
    padding: 6px 10px;
    background: var(--input-bg);
    border: 1px solid var(--card-border);
    border-radius: 8px;
    font-size: 0.85rem;
    font-family: monospace;
    color: var(--accent);
    cursor: grab;
    user-select: none;
}
.platzhalter-chip:active { cursor: grabbing; }
.platzhalter-chip:hover { background: var(--card-hover); }
.editSprachtext-drop { outline: 2px dashed var(--accent); outline-offset: 2px; }

.wochentage-row { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 8px; }
.wochentage-row label {
    display: flex; align-items: center; gap: 8px; margin: 0;
    cursor: pointer;
    min-height: var(--touch);
    padding: 4px 0;
    color: var(--text);
}
.wochentage-row input[type="checkbox"] { width: 22px; height: 22px; }

.form-actions { margin-top: 22px; display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.form-actions .header-btn { min-height: var(--touch); padding: 0 18px; background: var(--card-hover); border: 1px solid var(--card-border); color: var(--text); border-radius: 10px; font-size: 1rem; }
.form-actions .header-btn:hover { opacity: 0.9; }
.form-actions .header-btn.danger { background: rgba(220,38,38,0.15); color: var(--danger); border-color: transparent; }
[data-theme="dark"] .form-actions .header-btn.danger { background: rgba(248,81,73,0.2); }

.platzhalter-hint { font-size: 0.8rem; color: var(--text-muted); margin-top: 6px; line-height: 1.4; }

.audio-section .audio-groups { margin-top: 10px; }
.audio-group { margin-bottom: 14px; }
.audio-group-title { font-size: 0.875rem; font-weight: 600; color: var(--text); display: block; margin-bottom: 6px; }
.audio-list { list-style: none; margin: 0 0 8px 0; padding: 0; }
.audio-list li { display: flex; align-items: center; justify-content: space-between; padding: 6px 10px; background: var(--input-bg); border-radius: 8px; margin-bottom: 4px; font-size: 0.875rem; }
.audio-list li span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.audio-list li button { flex-shrink: 0; margin-left: 8px; padding: 2px 8px; font-size: 0.8rem; background: var(--card-hover); border: 1px solid var(--card-border); border-radius: 6px; color: var(--text); cursor: pointer; }
.audio-add { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.audio-add input[type="text"] { flex: 1; min-width: 120px; padding: 8px 10px; font-size: 0.875rem; background: var(--input-bg); border: 1px solid var(--card-border); border-radius: 8px; }

#settingsOverlay .overlay-box {
    max-width: 580px;
    min-height: 480px;
    min-height: 52dvh;
    display: flex;
    flex-direction: column;
}
#settingsOverlay #settingsBoxContent {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
#settingsOverlay .settings-tabs-wrapper {
    flex-shrink: 0;
}
#settingsOverlay .settings-tab-panels {
    flex: 1;
    min-height: 320px;
    min-height: 36dvh;
    overflow-y: auto;
}
#settingsOverlay #settingsBoxContent > h2,
#settingsOverlay #settingsBoxContent > #settingsMsg,
#settingsOverlay #settingsBoxContent > .form-actions { flex-shrink: 0; }
.settings-tabs {
    display: flex;
    gap: 0;
    margin: -20px -20px 16px -20px;
    padding: 0 12px;
    background: var(--card-hover);
    border-bottom: 1px solid var(--card-border);
    flex-wrap: wrap;
}
.settings-tabs button {
    padding: 12px 14px;
    border: none;
    background: none;
    color: var(--text-muted);
    font-size: 0.9rem;
    font-family: inherit;
    cursor: pointer;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}
.settings-tabs button:hover { color: var(--text); }
.settings-tabs button.active {
    color: var(--accent);
    font-weight: 500;
    border-bottom-color: var(--accent);
}
.settings-tab-panel { display: none; }
.settings-tab-panel.active { display: block; }
.settings-section { margin-bottom: 22px; }
.settings-section h3 { margin: 0 0 10px 0; font-size: 0.95rem; color: var(--accent); }
.tts-custom-list { margin: 10px 0; }
.tts-custom-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 12px 14px;
    background: var(--input-bg);
    border-radius: 12px;
    margin-bottom: 8px;
    border: 1px solid var(--card-border);
    gap: 10px;
}
.tts-custom-item span { font-size: 0.9rem; flex: 1; min-width: 0; color: var(--text); }
.tts-custom-item code { color: var(--accent); }
.tts-custom-item .btn-small { min-height: 40px; padding: 0 14px; font-size: 0.875rem; background: var(--card-hover); border: 1px solid var(--card-border); color: var(--text); border-radius: 8px; }
.settings-msg { margin-top: 12px; font-size: 0.9rem; color: var(--accent); }
.settings-msg.error { color: var(--danger); }
.accent-color-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.accent-color-row input[type="color"] { width: 44px; height: 44px; padding: 2px; border: 1px solid var(--card-border); border-radius: 8px; cursor: pointer; background: var(--input-bg); }
.accent-hex-input { width: 90px; padding: 8px 10px; background: var(--input-bg); border: 1px solid var(--card-border); border-radius: 8px; color: var(--text); font-family: monospace; font-size: 0.9rem; }
.icons-upload-list { display: flex; flex-direction: column; gap: 14px; }
.icons-upload-row { display: flex; align-items: center; gap: 14px; padding: 12px; background: var(--input-bg); border: 1px solid var(--card-border); border-radius: 10px; }
.icons-upload-preview { width: 48px; height: 48px; flex-shrink: 0; border-radius: 8px; overflow: hidden; background: var(--card-hover); }
.icons-upload-preview img { width: 100%; height: 100%; object-fit: contain; }
.icons-upload-info { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.icons-upload-label { font-weight: 500; color: var(--text); }
.icons-upload-badge { font-size: 0.8rem; color: var(--text-muted); }
.icons-upload-badge:not(.default) { color: var(--accent); }
.icons-upload-actions { display: flex; gap: 8px; flex-shrink: 0; }

.update-version-display { font-weight: 600; font-size: 1rem; margin: 4px 0; color: var(--text); }
.update-log-wrap { margin-top: 16px; }
.update-log-wrap label { display: block; margin-bottom: 6px; color: var(--text-muted); font-size: 0.875rem; }
.update-log { max-height: 180px; overflow: auto; background: var(--input-bg); padding: 12px; border-radius: 8px; font-size: 12px; font-family: monospace; white-space: pre-wrap; margin: 0; border: 1px solid var(--card-border); }

.zeitzone-dropdown-wrap { position: relative; }
.zeitzone-list {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    margin: 2px 0 0 0;
    padding: 0;
    list-style: none;
    max-height: 220px;
    overflow-y: auto;
    background: var(--card);
    border: 1px solid var(--card-border);
    border-radius: 10px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.15);
    z-index: 50;
    display: none;
}
.zeitzone-list[aria-hidden="false"] { display: block; }
.zeitzone-list li {
    padding: 10px 12px;
    font-size: 0.9rem;
    color: var(--text);
    cursor: pointer;
    border-bottom: 1px solid var(--card-border);
}
.zeitzone-list li:last-child { border-bottom: none; }
.zeitzone-list li:hover { background: var(--card-hover); }
.zeitzone-list li.zeitzone-hidden { display: none; }

/* In-Overlay-Ladeanzeige */
.overlay-loading {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 2rem;
    color: var(--text-muted);
}
.overlay-loading .spinner-small {
    width: 32px;
    height: 32px;
    border: 3px solid var(--card-border);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: loadingSpin 0.9s linear infinite;
}
.overlay-loading .text { margin-top: 10px; font-size: 0.9rem; }

/* Sleep-Mode Vollbild – 3 Spalten wie Screen-Saver (Aktuell | 8-Tage | Details) */
#sleepModeOverlay {
    position: fixed;
    inset: 0;
    z-index: 200;
    background: var(--bg);
    display: none;
    flex-direction: column;
    padding: max(var(--safe-top), 16px) 16px max(16px, var(--safe-bottom));
    box-sizing: border-box;
}
#sleepModeOverlay.visible { display: flex; }
#sleepModeOverlay .sleep-panels {
    display: flex;
    flex: 1;
    min-height: 0;
    gap: 12px;
}
#sleepModeOverlay .sleep-left,
#sleepModeOverlay .sleep-center,
#sleepModeOverlay .sleep-right {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    background: var(--card);
    border: 1px solid var(--card-border);
    border-radius: 16px;
    padding: 16px;
    overflow: hidden;
}
#sleepModeOverlay .sleep-title {
    font-size: 0.8rem;
    color: var(--text-muted);
    margin-bottom: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
/* Linke Spalte: Aktuelles Wetter */
#sleepModeOverlay .sleep-now-icon-wrap {
    display: flex;
    justify-content: center;
    margin: 12px 0;
}
#sleepModeOverlay .sleep-now-icon {
    width: 80px;
    height: 80px;
    object-fit: contain;
}
#sleepModeOverlay .sleep-now-desc {
    font-size: 0.9rem;
    color: var(--text-muted);
    text-align: center;
    margin-bottom: 8px;
}
#sleepModeOverlay .sleep-now-temp {
    font-size: 2.5rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--text);
    text-align: center;
    line-height: 1.1;
}
#sleepModeOverlay .sleep-highlow {
    display: flex;
    gap: 12px;
    justify-content: center;
    margin: 12px 0;
    flex-wrap: wrap;
}
#sleepModeOverlay .sleep-highlow span {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 0.95rem;
    color: var(--text-muted);
}
#sleepModeOverlay .sleep-strip {
    display: flex;
    flex-wrap: wrap;
    gap: 12px 16px;
    margin-top: auto;
    padding-top: 12px;
    border-top: 1px solid var(--card-border);
    font-size: 0.85rem;
    color: var(--text-muted);
}
#sleepModeOverlay .sleep-strip span { display: inline-flex; align-items: center; gap: 4px; }
/* Mittlere Spalte: 8-Tage-Vorschau */
#sleepModeOverlay .sleep-forecast-list {
    flex: 1;
    overflow-y: auto;
    min-height: 0;
}
#sleepModeOverlay .sleep-forecast-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 0;
    border-bottom: 1px solid var(--card-border);
    font-size: 0.9rem;
    color: var(--text);
}
#sleepModeOverlay .sleep-forecast-row:last-child { border-bottom: none; }
#sleepModeOverlay .sleep-forecast-icon {
    width: 28px;
    height: 28px;
    object-fit: contain;
    flex-shrink: 0;
}
#sleepModeOverlay .sleep-forecast-date { min-width: 36px; color: var(--text); }
#sleepModeOverlay .sleep-forecast-temp { font-weight: 600; font-variant-numeric: tabular-nums; }
#sleepModeOverlay .sleep-sun {
    display: flex;
    gap: 16px;
    margin-top: 12px;
    padding-top: 12px;
    border-top: 1px solid var(--card-border);
    font-size: 0.9rem;
    color: var(--text-muted);
}
#sleepModeOverlay .sleep-sun span { display: inline-flex; align-items: center; gap: 6px; }
/* Rechte Spalte: Details */
#sleepModeOverlay .sleep-details {
    display: flex;
    flex-direction: column;
    gap: 8px;
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}
#sleepModeOverlay .sleep-detail-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    font-size: 0.9rem;
    color: var(--text);
}
#sleepModeOverlay .sleep-detail-row .label { color: var(--text-muted); }
#sleepModeOverlay .sleep-detail-row .value { font-variant-numeric: tabular-nums; }
#sleepModeOverlay .sleep-pm {
    margin-top: auto;
    padding-top: 12px;
    border-top: 1px solid var(--card-border);
    font-size: 0.8rem;
    color: var(--text-muted);
}
/* Wecker-Leiste unten */
#sleepModeOverlay .sleep-wecker-bar {
    margin-top: 10px;
    padding: 10px 14px;
    background: var(--card);
    border: 1px solid var(--card-border);
    border-radius: 12px;
    flex-shrink: 0;
}
#sleepModeOverlay .sleep-wecker-bar .sleep-title { margin-bottom: 6px; }
#sleepModeOverlay .sleep-wecker-item {
    font-size: 0.95rem;
    padding: 4px 0;
    color: var(--text);
}
#sleepModeOverlay .sleep-wecker-zeit { font-weight: 600; font-variant-numeric: tabular-nums; }
#sleepModeOverlay .sleep-wecker-meta { font-size: 0.8rem; color: var(--text-muted); margin-top: 2px; }
#sleepModeOverlay .sleep-wetter-tag { font-size: 0.9rem; padding: 6px 0; color: var(--text-muted); }

/* Wetter-Tab: gleiches 3-Spalten-Design wie Sleep-Mode */
#panelWetter.panel.active { display: flex; flex-direction: column; }
#panelWetter .panel-inner.wetter-dashboard {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    padding: 16px;
    max-width: none;
}
#panelWetter .panel-inner .sleep-panels {
    display: flex;
    flex: 1;
    min-height: 0;
    gap: 12px;
}
#panelWetter .panel-inner .sleep-left,
#panelWetter .panel-inner .sleep-center,
#panelWetter .panel-inner .sleep-right {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    background: var(--card);
    border: 1px solid var(--card-border);
    border-radius: 16px;
    padding: 16px;
    overflow: hidden;
}
#panelWetter .panel-inner .sleep-title { font-size: 0.8rem; color: var(--text-muted); margin-bottom: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; }
#panelWetter .panel-inner .sleep-now-icon-wrap { display: flex; justify-content: center; margin: 12px 0; }
#panelWetter .panel-inner .sleep-now-icon { width: 80px; height: 80px; object-fit: contain; }
#panelWetter .panel-inner .sleep-now-desc { font-size: 0.9rem; color: var(--text-muted); text-align: center; margin-bottom: 8px; }
#panelWetter .panel-inner .sleep-now-temp { font-size: 2.5rem; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--text); text-align: center; line-height: 1.1; }
#panelWetter .panel-inner .sleep-highlow { display: flex; gap: 12px; justify-content: center; margin: 12px 0; flex-wrap: wrap; }
#panelWetter .panel-inner .sleep-highlow span { display: inline-flex; align-items: center; gap: 4px; font-size: 0.95rem; color: var(--text-muted); }
#panelWetter .panel-inner .sleep-strip { display: flex; flex-wrap: wrap; gap: 12px 16px; margin-top: auto; padding-top: 12px; border-top: 1px solid var(--card-border); font-size: 0.85rem; color: var(--text-muted); }
#panelWetter .panel-inner .sleep-strip span { display: inline-flex; align-items: center; gap: 4px; }
#panelWetter .panel-inner .sleep-forecast-list { flex: 1; overflow-y: auto; min-height: 0; }
#panelWetter .panel-inner .sleep-forecast-row { display: flex; align-items: center; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--card-border); font-size: 0.9rem; color: var(--text); }
#panelWetter .panel-inner .sleep-forecast-row:last-child { border-bottom: none; }
#panelWetter .panel-inner .sleep-forecast-icon { width: 28px; height: 28px; object-fit: contain; flex-shrink: 0; }
#panelWetter .panel-inner .sleep-forecast-date { min-width: 36px; color: var(--text); }
#panelWetter .panel-inner .sleep-forecast-temp { font-weight: 600; font-variant-numeric: tabular-nums; }
#panelWetter .panel-inner .sleep-sun { display: flex; gap: 16px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--card-border); font-size: 0.9rem; color: var(--text-muted); }
#panelWetter .panel-inner .sleep-sun span { display: inline-flex; align-items: center; gap: 6px; }
#panelWetter .panel-inner .sleep-details { display: flex; flex-direction: column; gap: 8px; flex: 1; min-height: 0; overflow-y: auto; }
#panelWetter .panel-inner .sleep-detail-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 0.9rem; color: var(--text); }
#panelWetter .panel-inner .sleep-detail-row .label { color: var(--text-muted); }
#panelWetter .panel-inner .sleep-detail-row .value { font-variant-numeric: tabular-nums; }
#panelWetter .panel-inner .sleep-pm { margin-top: auto; padding-top: 12px; border-top: 1px solid var(--card-border); font-size: 0.8rem; color: var(--text-muted); }
#panelWetter .panel-inner .sleep-wetter-tag { font-size: 0.9rem; padding: 6px 0; color: var(--text-muted); }
</style>
</head>
<body>

<div id="loadingScreen" aria-live="polite" aria-busy="true">
    <div class="spinner" aria-hidden="true"></div>
    <span class="text">Laden…</span>
</div>

<div id="panelAlarm" class="panel active">
<header class="panel-header">
    <div class="header-top">
        <div>
            <h1 class="header-title" id="headerTitle">Alarm</h1>
            <p class="header-subtitle" id="headerSubtitle">–</p>
        </div>
        <div class="header-actions">
            <button type="button" class="header-btn" id="btnNeuWecker" aria-label="Neuer Alarm">+</button>
            <button type="button" class="header-btn btn-open-settings" aria-label="Einstellungen"><img src="/icon.php?name=settings.svg" alt="" class="header-btn-icon theme-icon" width="22" height="22" aria-hidden="true"></button>
            <button type="button" class="header-btn" id="themeToggle" aria-label="Dark/Light Mode" title="Dark/Light Mode"></button>
        </div>
    </div>
</header>
<main class="main" id="weckerListe">
<?php
foreach ($wecker as $w):
    $aktiv = (bool)($w['aktiv'] ?? 0);
    $wochentage = ($w['wiederholung'] ?? '') === 'woechentlich' ? function_wecker_hole_wochentage((int)$w['id']) : [];
    if (($w['wiederholung'] ?? '') === 'woechentlich' && !empty($wochentage)) {
        $meta = implode(' ', array_map(function ($t) use ($wochentage_labels) { return $wochentage_labels[$t][0] ?? ''; }, $wochentage));
    } elseif (($w['wiederholung'] ?? '') === 'einmal' && !empty($w['weckdatum'])) {
        $d = DateTime::createFromFormat('Y-m-d', $w['weckdatum']);
        $monate = [1=>'Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'];
        $tage_kurz = ['Sun'=>'So','Mon'=>'Mo','Tue'=>'Di','Wed'=>'Mi','Thu'=>'Do','Fri'=>'Fr','Sat'=>'Sa'];
        if ($d) {
            $meta = ($tage_kurz[$d->format('D')] ?? $d->format('D')) . ', ' . $d->format('j') . '. ' . ($monate[(int)$d->format('n')] ?? $d->format('F'));
        } else {
            $meta = '';
        }
    } else {
        $meta = 'Täglich';
    }
    $label = $w['sprachtext'] ? (mb_strlen($w['sprachtext']) > 25 ? mb_substr($w['sprachtext'], 0, 22) . '…' : $w['sprachtext']) : '';
?>
    <div class="wecker <?= $aktiv ? '' : 'inaktiv' ?>" data-id="<?= (int)$w['id'] ?>" onclick="editWecker(<?= (int)$w['id'] ?>)">
        <div class="wecker-left">
            <?php if ($label !== ''): ?><span class="wecker-label"><?= htmlspecialchars($label) ?></span><?php endif; ?>
            <div class="weckzeit"><?= htmlspecialchars(substr($w['weckzeit'], 0, 5)) ?></div>
            <div class="wecker-meta"><?= htmlspecialchars($meta) ?></div>
        </div>
        <label class="toggle" onclick="event.stopPropagation()">
            <input type="checkbox" <?= $aktiv ? 'checked' : '' ?> onchange="toggleWecker(<?= (int)$w['id'] ?>, this.checked)">
            <span></span>
        </label>
    </div>
<?php endforeach; ?>
</main>
</div>

<div id="panelTimer" class="panel">
    <header class="panel-header">
        <div class="header-top">
            <div>
                <h1 class="header-title">Timer</h1>
                <p class="header-subtitle header-subtitle-empty">–</p>
            </div>
            <div class="header-actions">
                <button type="button" class="header-btn btn-open-settings" aria-label="Einstellungen"><img src="/icon.php?name=settings.svg" alt="" class="header-btn-icon theme-icon" width="22" height="22" aria-hidden="true"></button>
                <button type="button" class="header-btn" id="themeToggle4" aria-label="Dark/Light Mode"></button>
            </div>
        </div>
    </header>
    <div class="panel-inner timer-inner">
        <div id="timerPresets" class="timer-presets" aria-label="Timer-Voreinstellungen"></div>
        <div class="timer-set">
            <input type="number" id="timerMin" min="0" max="99" value="5" placeholder="Min">
            <span>:</span>
            <input type="number" id="timerSek" min="0" max="59" value="0" placeholder="Sek">
        </div>
        <div id="timerAnzeige" class="timer-anzeige" style="display:none;">05:00</div>
        <div class="timer-buttons">
            <button type="button" class="header-btn" id="timerStart">Start</button>
            <button type="button" class="header-btn" id="timerStop" style="display:none;">Stop</button>
        </div>
    </div>
</div>

<div id="timerPresetEditOverlay" class="overlay" role="dialog" aria-modal="true" aria-labelledby="timerPresetEditTitle">
    <div id="timerPresetEditBox" class="overlay-box" onclick="event.stopPropagation()">
        <h3 id="timerPresetEditTitle">Voreinstellung bearbeiten</h3>
        <label class="timer-preset-edit-label" for="timerPresetEditMin">Minuten</label>
        <input type="number" id="timerPresetEditMin" class="timer-preset-edit-input" min="1" max="99" value="15" placeholder="Min">
        <div class="timer-preset-edit-actions">
            <button type="button" class="header-btn" id="timerPresetEditCancel">Abbrechen</button>
            <button type="button" class="header-btn header-btn-primary" id="timerPresetEditSave">Speichern</button>
        </div>
    </div>
</div>

<div id="panelWetter" class="panel">
    <header class="panel-header">
        <div class="header-top">
            <div>
                <h1 class="header-title">Wetter</h1>
                <p class="header-subtitle" id="wetterSubtitle">Ort in Einstellungen festlegen</p>
            </div>
            <div class="header-actions">
                <button type="button" class="header-btn" id="wetterAktualisieren" aria-label="Aktualisieren">Aktualisieren</button>
                <button type="button" class="header-btn btn-open-settings" aria-label="Einstellungen"><img src="/icon.php?name=settings.svg" alt="" class="header-btn-icon theme-icon" width="22" height="22" aria-hidden="true"></button>
                <button type="button" class="header-btn" id="themeToggle5" aria-label="Dark/Light Mode"></button>
            </div>
        </div>
    </header>
    <div class="panel-inner wetter-dashboard">
        <div class="sleep-panels">
            <div class="sleep-left" id="wetterAktuell"></div>
            <div class="sleep-center" id="wetterForecast"></div>
            <div class="sleep-right" id="wetterDetails"></div>
        </div>
    </div>
</div>

<div id="panelMusik" class="panel">
    <header class="panel-header">
        <div class="header-top">
            <div>
                <h1 class="header-title">Meine Musik</h1>
                <p class="header-subtitle">Hochladen, abspielen, löschen</p>
            </div>
            <div class="header-actions">
                <button type="button" class="header-btn btn-open-settings" aria-label="Einstellungen"><img src="/icon.php?name=settings.svg" alt="" class="header-btn-icon theme-icon" width="22" height="22" aria-hidden="true"></button>
                <button type="button" class="header-btn" id="themeToggleMusik" aria-label="Dark/Light Mode"></button>
            </div>
        </div>
    </header>
    <div class="panel-inner musik-inner">
        <div class="musik-alben-section">
            <h3 class="musik-list-title">Playlists</h3>
            <p class="platzhalter-hint">Playlist anlegen, dann beim Hochladen oder unter Bearbeiten Songs zuordnen (ein Song kann in mehreren Playlists sein).</p>
            <div class="musik-alben-create">
                <input type="text" id="musikAlbumNeu" class="musik-album-input" placeholder="Neue Playlist" maxlength="255">
                <button type="button" class="header-btn" id="musikAlbumErstellen">Erstellen</button>
            </div>
            <ul id="musikAlbenListe" class="musik-alben-liste"></ul>
        </div>
        <div id="albumEditOverlay" class="musik-edit-overlay" aria-hidden="true">
            <div class="musik-edit-box">
                <h3 class="musik-edit-title">Playlist bearbeiten</h3>
                <input type="hidden" id="albumEditId" value="">
                <label class="musik-edit-label">Name</label>
                <input type="text" id="albumEditName" class="musik-edit-input" maxlength="255" placeholder="Playlistname">
                <label class="musik-edit-label">Icon</label>
                <div class="album-edit-icon-row">
                    <div class="musik-cover-wrap album-edit-cover"><img id="albumEditPreview" src="" alt="" width="48" height="48" onerror="this.style.display='none'"></div>
                    <div class="album-edit-icon-actions">
                        <input type="file" id="albumEditIconInput" accept=".svg,.png,.jpg,.jpeg,.gif,.webp,image/*" style="display:none">
                        <button type="button" class="header-btn" id="albumEditIconBtn">Icon hochladen</button>
                        <button type="button" class="header-btn" id="albumEditIconRemove" style="display:none">Icon entfernen</button>
                    </div>
                </div>
                <p class="platzhalter-hint">Kein Icon: Es wird zufällig ein Song-Cover aus der Playlist angezeigt.</p>
                <div class="musik-edit-actions">
                    <button type="button" class="header-btn" id="albumEditCancel">Abbrechen</button>
                    <button type="button" class="header-btn header-btn-primary" id="albumEditSave">Speichern</button>
                </div>
            </div>
        </div>
        <div class="musik-upload">
            <label class="musik-upload-label">Neue Dateien hochladen</label>
            <div class="musik-upload-row">
                <input type="file" id="musikFileInput" accept=".mp3,.wav,.ogg,.m4a,.aac" multiple style="display:none">
                <button type="button" class="header-btn" id="musikUploadBtn">Dateien wählen …</button>
                <label class="musik-album-label">Playlists (optional, Mehrfachauswahl)</label>
                <div id="musikPlaylistCheckboxes" class="musik-playlist-checkboxes"></div>
            </div>
            <span id="musikUploadStatus" class="musik-upload-status"></span>
        </div>
        <div class="musik-list-wrap">
            <h3 class="musik-list-title">Deine Dateien</h3>
            <ul id="musikListe" class="musik-liste"></ul>
            <p id="musikListeLeer" class="platzhalter-hint" style="display:none;">Noch keine Musik hochgeladen. Nutze „Datei wählen“ zum Hochladen (MP3, WAV, OGG, M4A).</p>
        </div>
        <div id="musikEditOverlay" class="musik-edit-overlay" aria-hidden="true">
            <div class="musik-edit-box">
                <h3 class="musik-edit-title">Titel &amp; Playlists bearbeiten</h3>
                <input type="hidden" id="musikEditId" value="">
                <label class="musik-edit-label">Titel</label>
                <input type="text" id="musikEditTitel" class="musik-edit-input" maxlength="255" placeholder="Titel">
                <label class="musik-edit-label">Playlists (Mehrfachauswahl)</label>
                <div id="musikEditPlaylists" class="musik-edit-playlists"></div>
                <div class="musik-edit-actions">
                    <button type="button" class="header-btn" id="musikEditCancel">Abbrechen</button>
                    <button type="button" class="header-btn header-btn-primary" id="musikEditSave">Speichern</button>
                </div>
            </div>
        </div>
    </div>
</div>

<!-- Sleep-Mode Vollbild: 3 Spalten (Aktuell | 8-Tage | Details) + Wecker-Leiste -->
<div id="sleepModeOverlay" class="sleep-mode-overlay" aria-hidden="true">
    <div class="sleep-panels">
        <div class="sleep-left" id="sleepWetterAktuell"></div>
        <div class="sleep-center" id="sleepWetterForecast"></div>
        <div class="sleep-right" id="sleepWetterDetails"></div>
    </div>
    <div class="sleep-wecker-bar">
        <div class="sleep-title">Nächste Wecker</div>
        <div id="sleepWeckerListe"></div>
    </div>
</div>

<nav class="bottom-nav" aria-label="Hauptnavigation">
    <button type="button" data-panel="panelAlarm" class="active">Alarm</button>
    <button type="button" data-panel="panelTimer">Timer</button>
    <button type="button" data-panel="panelWetter">Wetter</button>
    <button type="button" data-panel="panelMusik">Musik</button>
</nav>

<div id="updateToast" class="update-toast" aria-live="polite" role="status" style="display:none">
    <div class="update-toast-inner">
        <p class="update-toast-text">Update verfügbar</p>
        <div class="update-toast-actions">
            <button type="button" class="header-btn" id="updateToastToSettings">Zu Einstellungen</button>
            <button type="button" class="header-btn update-toast-close" id="updateToastClose" aria-label="Schließen">&times;</button>
        </div>
    </div>
</div>

<!-- Edit Overlay -->
<div id="editOverlay" class="overlay" onclick="closeEdit()">
    <div id="editBox" class="overlay-box" onclick="event.stopPropagation()">
        <div id="editOverlayLoading" class="overlay-loading" style="display:none;">
            <div class="spinner-small"></div>
            <span class="text">Wecker wird geladen…</span>
        </div>
        <div id="editBoxContent">
        <h2 id="editTitle">Wecker bearbeiten</h2>
        <form id="editForm" onsubmit="return saveWecker(event)">
            <input type="hidden" name="id" id="editId" value="0">
            <div class="form-row">
                <label>Weckzeit</label>
                <input type="time" name="weckzeit" id="editWeckzeit" required>
            </div>
            <div class="form-row">
                <label>Wiederholung</label>
                <select name="wiederholung" id="editWiederholung" onchange="toggleWochentageVisibility()">
                    <option value="einmal">Einmal</option>
                    <option value="taeglich">Täglich</option>
                    <option value="woechentlich">Wöchentlich</option>
                </select>
            </div>
            <div class="form-row" id="rowWeckdatum">
                <label>Datum (nur bei „Einmal“)</label>
                <input type="date" name="weckdatum" id="editWeckdatum">
            </div>
            <div class="form-row" id="rowWochentage" style="display:none">
                <label>Wochentage</label>
                <div class="wochentage-row">
                    <?php foreach ($wochentage_labels as $num => $label): ?>
                    <label><input type="checkbox" name="wochentage[]" value="<?= $num ?>"> <?= $label ?></label>
                    <?php endforeach; ?>
                </div>
            </div>
            <div class="form-row">
                <label>Sprachtext (TTS)</label>
                <textarea name="sprachtext" id="editSprachtext" required></textarea>
                <p class="platzhalter-hint">Platzhalter per Drag &amp; Drop in den Text ziehen:</p>
                <div class="platzhalter-chips" id="editPlatzhalterChips">
                    <?php
                    $alle_platzhalter = [];
                    foreach ($platzhalter_system as $p) {
                        $v = $p['platzhalter'] ?? '';
                        if ($v !== '') $alle_platzhalter[] = (strpos($v, '{{') !== false ? $v : '{{' . $v . '}}');
                    }
                    foreach ($platzhalter_custom as $p) {
                        $v = $p['platzhalter'] ?? '';
                        if ($v !== '') $alle_platzhalter[] = (strpos($v, '{{') !== false ? $v : '{{' . $v . '}}');
                    }
                    $alle_platzhalter = array_unique($alle_platzhalter);
                    foreach ($alle_platzhalter as $ph):
                    ?><span class="platzhalter-chip" draggable="true" data-placeholder="<?= htmlspecialchars($ph) ?>" title="Ziehen und im Text ablegen"><?= htmlspecialchars($ph) ?></span><?php endforeach; ?>
                </div>
            </div>
            <div class="form-row audio-section">
                <label>Musik / Lieder</label>
                <p class="platzhalter-hint">Optional: Lieder, die vor dem TTS, gleichzeitig oder danach abgespielt werden.</p>
                <div class="audio-groups">
                    <div class="audio-group">
                        <span class="audio-group-title">Vorher</span>
                        <ul id="audioListVorher" class="audio-list"></ul>
                        <div class="audio-add">
                            <input type="text" id="audioPfadVorher" placeholder="Pfad oder URL (z.B. /WAV/song.mp3)">
                            <input type="text" id="audioTitelVorher" placeholder="Titel (optional)">
                            <button type="button" class="header-btn btn-small" onclick="addAudio('vorher')">+</button>
                        </div>
                    </div>
                    <div class="audio-group">
                        <span class="audio-group-title">Gleichzeitig mit TTS</span>
                        <ul id="audioListGleichzeitig" class="audio-list"></ul>
                        <div class="audio-add">
                            <input type="text" id="audioPfadGleichzeitig" placeholder="Pfad oder URL">
                            <input type="text" id="audioTitelGleichzeitig" placeholder="Titel (optional)">
                            <button type="button" class="header-btn btn-small" onclick="addAudio('gleichzeitig')">+</button>
                        </div>
                    </div>
                    <div class="audio-group">
                        <span class="audio-group-title">Danach</span>
                        <ul id="audioListDanach" class="audio-list"></ul>
                        <div class="audio-add">
                            <input type="text" id="audioPfadDanach" placeholder="Pfad oder URL">
                            <input type="text" id="audioTitelDanach" placeholder="Titel (optional)">
                            <button type="button" class="header-btn btn-small" onclick="addAudio('danach')">+</button>
                        </div>
                    </div>
                </div>
                <input type="hidden" name="audio" id="editAudio" value="[]">
            </div>
            <div class="form-actions">
                <button type="submit" class="header-btn">Speichern</button>
                <button type="button" class="header-btn" onclick="closeEdit()">Abbrechen</button>
                <button type="button" id="btnDeleteWecker" class="header-btn danger" onclick="deleteWecker()" style="display:none">Löschen</button>
            </div>
        </form>
        </div>
    </div>
</div>

<!-- Settings Overlay -->
<div id="settingsOverlay" class="overlay" onclick="closeSettings(event)">
    <div class="overlay-box" onclick="event.stopPropagation()" style="position:relative;">
        <div id="settingsOverlayLoading" class="overlay-loading" style="display:none; position:absolute; inset:0; background:var(--card); border-radius:12px;">
            <div class="spinner-small"></div>
            <span class="text">Einstellungen werden geladen…</span>
        </div>
        <div id="settingsBoxContent">
        <h2>Einstellungen</h2>
        <div class="settings-tabs-wrapper">
        <nav class="settings-tabs" role="tablist" aria-label="Einstellungsbereiche">
            <button type="button" role="tab" id="settingsTabBtnAllgemein" aria-selected="true" aria-controls="settingsTabAllgemein" data-tab="settingsTabAllgemein" class="active">Allgemein</button>
            <button type="button" role="tab" id="settingsTabBtnTTS" aria-selected="false" aria-controls="settingsTabTTS" data-tab="settingsTabTTS">TTS</button>
            <button type="button" role="tab" id="settingsTabBtnWetter" aria-selected="false" aria-controls="settingsTabWetter" data-tab="settingsTabWetter">Wetter</button>
            <button type="button" role="tab" id="settingsTabBtnPlatzhalter" aria-selected="false" aria-controls="settingsTabPlatzhalter" data-tab="settingsTabPlatzhalter">Platzhalter</button>
            <button type="button" role="tab" id="settingsTabBtnUpdate" aria-selected="false" aria-controls="settingsTabUpdate" data-tab="settingsTabUpdate">Update</button>
        </nav>
        </div>
        <div class="settings-tab-panels">
        <div id="settingsTabAllgemein" class="settings-tab-panel active" role="tabpanel" aria-labelledby="settingsTabBtnAllgemein">
            <div class="settings-section">
                <h3>Zeitzone</h3>
                <div class="form-row zeitzone-dropdown-wrap">
                    <input type="text" id="settingZeitzone" placeholder="Suchen oder auswählen…" autocomplete="off" role="combobox" aria-expanded="false" aria-autocomplete="list">
                    <ul id="zeitzoneList" class="zeitzone-list" role="listbox" aria-hidden="true"></ul>
                </div>
            </div>
            <div class="settings-section">
                <h3>Akzentfarbe</h3>
                <div class="form-row form-row-accent">
                    <label>Farbe für Buttons, Links und Hervorhebungen</label>
                    <div class="accent-color-row">
                        <input type="color" id="settingAccentColor" value="#2563eb" title="Akzentfarbe wählen">
                        <input type="text" id="settingAccentColorHex" class="accent-hex-input" placeholder="#2563eb" maxlength="7" pattern="^#[0-9a-fA-F]{3,6}$">
                        <button type="button" class="header-btn" id="settingAccentReset" title="Standard zurücksetzen">Standard</button>
                    </div>
                </div>
            </div>
            <div class="settings-section">
                <h3>Sleep-Mode</h3>
                <div class="form-row">
                    <label>Sleep-Mode starten nach … Sekunden (0 = aus)</label>
                    <input type="number" id="settingSleepModeSekunden" min="0" max="86400" value="0" placeholder="z.B. 30">
                    <p class="platzhalter-hint">Nach dieser Zeit ohne Berührung wird der Sleep-Mode angezeigt (nur bei Aufruf über localhost bzw. 127.0.0.1 – nicht auf Handy/LAN). Tippen zum Beenden.</p>
                </div>
            </div>
        </div>

        <div id="settingsTabTTS" class="settings-tab-panel" role="tabpanel" aria-labelledby="settingsTabBtnTTS">
            <div class="settings-section">
                <h3>TTS</h3>
                <div class="form-row">
                    <label>Ihr Name (für TTS)</label>
                    <input type="text" id="settingTtsName" placeholder="z.B. Max" maxlength="100" autocomplete="name">
                    <p class="platzhalter-hint">Im Sprachtext können Sie <code>{{name}}</code> verwenden – wird beim Abspielen durch Ihren Namen ersetzt.</p>
                </div>
                <div class="form-row">
                    <label>Sprachmodell</label>
                    <select id="settingTtsModell">
                        <?php foreach ($tts_models as $m): ?>
                        <option value="<?= htmlspecialchars($m['id']) ?>"><?= htmlspecialchars($m['label']) ?></option>
                        <?php endforeach; ?>
                    </select>
                </div>
                <div class="form-row">
                    <label>TTS-Lautstärke (0–100)</label>
                    <input type="range" id="settingLautstaerke" min="0" max="100" value="100">
                    <span id="settingLautstaerkeVal">100</span> %
                </div>
                <div class="form-row">
                    <label>Musik-/Audio-Lautstärke (0–100)</label>
                    <input type="range" id="settingLautstaerkeMusik" min="0" max="100" value="80">
                    <span id="settingLautstaerkeMusikVal">80</span> %
                </div>
                <div class="form-row">
                    <label>Während TTS: Musik dämpfen auf … %</label>
                    <input type="range" id="settingMusikDucking" min="0" max="100" value="20">
                    <span id="settingMusikDuckingVal">20</span> %
                    <p class="platzhalter-hint">Beim TTS-Spruch wird die Musik auf diesen Wert reduziert, damit die Ansage gut hörbar ist.</p>
                </div>
            </div>
        </div>

        <div id="settingsTabWetter" class="settings-tab-panel" role="tabpanel" aria-labelledby="settingsTabBtnWetter">
            <div class="settings-section">
                <h3>Wetter</h3>
                <div class="form-row">
                    <label>Ort oder PLZ (Deutschland)</label>
                    <input type="text" id="settingWetterOrt" placeholder="z.B. 97478 oder Knetzgau" maxlength="100">
                    <p class="platzhalter-hint">Wetter-Anzeige auf der Wetter-Seite. PLZ oder Ortsname (z.B. Knetzgau).</p>
                </div>
            </div>
        </div>

        <div id="settingsTabPlatzhalter" class="settings-tab-panel" role="tabpanel" aria-labelledby="settingsTabBtnPlatzhalter">
            <div class="settings-section">
                <h3>Benutzerdefinierte TTS-Platzhalter</h3>
                <p class="platzhalter-hint">Diese Platzhalter können Sie im Wecker-Sprachtext verwenden (z.B. {{name}}).</p>
                <div id="ttsCustomList" class="tts-custom-list"></div>
                <div class="form-row">
                    <label>Platzhalter (z.B. weckername → {{weckername}})</label>
                    <input type="text" id="customPlatzhalter" placeholder="weckername">
                </div>
                <div class="form-row">
                    <label>Ersetzungstext</label>
                    <input type="text" id="customErsetzungstext" placeholder="Guten Morgen">
                </div>
                <div class="form-row">
                    <label>Beschreibung (optional)</label>
                    <input type="text" id="customBeschreibung" placeholder="Begrüßung">
                </div>
                <input type="hidden" id="customId" value="0">
                <button type="button" class="header-btn" id="btnCustomSave">Hinzufügen / Speichern</button>
            </div>
        </div>

        <div id="settingsTabUpdate" class="settings-tab-panel" role="tabpanel" aria-labelledby="settingsTabBtnUpdate">
            <div class="settings-section">
                <h3>Update</h3>
                <p class="platzhalter-hint">Aktuelle Version vom Update-Server installieren. Vorher wird ein Backup erstellt.</p>
                <div class="form-row">
                    <label>Aktuelle Version</label>
                    <p id="updateCurrentVersion" class="update-version-display">–</p>
                </div>
                <div class="form-row">
                    <label>Version installieren</label>
                    <select id="updateVersionSelect">
                        <option value="">Lade…</option>
                    </select>
                    <p class="platzhalter-hint" id="updateVersionHint"></p>
                </div>
                <div class="form-row">
                    <button type="button" class="header-btn" id="updateInstallBtn" disabled>Installieren</button>
                </div>
                <div id="updateLogWrap" class="update-log-wrap" style="display:none;">
                    <label>Log</label>
                    <pre id="updateLog" class="update-log"></pre>
                    <a href="#" id="updateReloadLink" style="display:none;">Seite neu laden</a>
                </div>
            </div>
        </div>

        </div>

        <div id="settingsMsg" class="settings-msg"></div>
        <div class="form-actions" style="margin-top:16px">
            <button type="button" class="header-btn" onclick="saveSettings()">Einstellungen speichern</button>
            <button type="button" class="header-btn" onclick="closeSettings()">Schließen</button>
        </div>
        </div>
    </div>
</div>

<!-- Bildschirmtastatur für Touch -->
<div id="touchKeyboard" class="touch-keyboard" aria-hidden="true" role="application" aria-label="Bildschirmtastatur"></div>

<script>
const API_WECKER = 'api_wecker.php';
const API_EINSTELLUNGEN = 'api_einstellungen.php';
const API_UPDATE = 'api_update.php';
const API_MUSIK = 'api_musik.php';
const API_ICONS = 'api_icons.php';
var weckerListeData = <?= json_encode($wecker_fuer_js) ?>;
const ZEITZONEN = <?= json_encode($zeitzonen) ?>;
var sleepModeSekunden = <?= (int)$sleep_mode_sekunden ?>;
/** Sleep-Mode nur auf localhost / 127.0.0.0/8 (nicht auf Handy/LAN) */
function isSleepModeHostErlaubt() {
    var h = (window.location.hostname || '').toLowerCase();
    return h === 'localhost' || h === '127.0.0.1' || /^127\.\d+\.\d+\.\d+$/.test(h);
}
var WOChentAGE_LABELS = { 1: 'Mo', 2: 'Di', 3: 'Mi', 4: 'Do', 5: 'Fr', 6: 'Sa', 7: 'So' };

/** QWERTZ-Layout für Bildschirmtastatur (Deutsch) */
var TOUCH_KEYBOARD_LAYOUT = [
    ['1','2','3','4','5','6','7','8','9','0','ß','´'],
    ['q','w','e','r','t','z','u','i','o','p','ü','+'],
    ['a','s','d','f','g','h','j','k','l','ö','ä','#'],
    ['Shift','z','x','c','v','b','n','m',',','.','-'],
    ['Space','Backspace','Fertig']
];

let audioList = [];
let zeitzoneListCloseTimer = null;

var touchKeyboardShift = false;
var touchKeyboardActiveEl = null;

function isTouchDevice() {
    return ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (window.matchMedia && window.matchMedia('(pointer: coarse)').matches);
}

function buildTouchKeyboard() {
    var container = document.getElementById('touchKeyboard');
    if (!container || container.children.length > 0) return container;
    TOUCH_KEYBOARD_LAYOUT.forEach(function(row, rowIdx) {
        var rowEl = document.createElement('div');
        rowEl.className = 'touch-keyboard-row';
        row.forEach(function(k) {
            var btn = document.createElement('button');
            btn.type = 'button';
            btn.className = 'touch-keyboard-key';
            if (k === 'Space') {
                btn.className += ' touch-keyboard-key--space';
                btn.setAttribute('data-key', ' ');
                btn.innerHTML = '&nbsp;';
                btn.setAttribute('aria-label', 'Leertaste');
            } else if (k === 'Backspace') {
                btn.className += ' touch-keyboard-key--wide touch-keyboard-key--action';
                btn.setAttribute('data-key', 'Backspace');
                btn.textContent = 'Löschen';
                btn.setAttribute('aria-label', 'Rücktaste');
            } else if (k === 'Fertig') {
                btn.className += ' touch-keyboard-key--wide touch-keyboard-key--action';
                btn.setAttribute('data-key', 'Fertig');
                btn.textContent = 'Fertig';
                btn.setAttribute('aria-label', 'Tastatur schließen');
            } else if (k === 'Shift') {
                btn.className += ' touch-keyboard-key--wide touch-keyboard-key--action';
                btn.setAttribute('data-key', 'Shift');
                btn.textContent = 'Umschalt';
                btn.setAttribute('aria-label', 'Großschreibung');
            } else {
                btn.setAttribute('data-key', k);
                btn.textContent = k;
            }
            btn.addEventListener('click', function() { handleTouchKey(btn.getAttribute('data-key')); });
            rowEl.appendChild(btn);
        });
        container.appendChild(rowEl);
    });
    return container;
}

function handleTouchKey(key) {
    var el = document.activeElement;
    if (!el || (el.tagName !== 'INPUT' && el.tagName !== 'TEXTAREA')) return;
    if (key === 'Fertig') {
        hideTouchKeyboard();
        el.blur();
        if (el.readOnly !== undefined) el.readOnly = false;
        return;
    }
    if (key === 'Shift') {
        touchKeyboardShift = !touchKeyboardShift;
        var kb = document.getElementById('touchKeyboard');
        if (kb) {
            var shiftBtn = kb.querySelector('[data-key="Shift"]');
            if (shiftBtn) shiftBtn.classList.toggle('active', touchKeyboardShift);
        }
        return;
    }
    if (key === 'Backspace') {
        var start = el.selectionStart, end = el.selectionEnd;
        if (start === end && start > 0) {
            el.value = el.value.slice(0, start - 1) + el.value.slice(end);
            el.selectionStart = el.selectionEnd = start - 1;
        } else if (start !== end) {
            el.value = el.value.slice(0, start) + el.value.slice(end);
            el.selectionStart = el.selectionEnd = start;
        }
        el.dispatchEvent(new Event('input', { bubbles: true }));
        return;
    }
    var char = key;
    if (key.length === 1 && /[a-zäöüß]/.test(key) && touchKeyboardShift) {
        char = key.toUpperCase();
        if (key === 'ä') char = 'Ä';
        if (key === 'ö') char = 'Ö';
        if (key === 'ü') char = 'Ü';
        if (key === 'ß') char = 'ß';
    }
    var start = el.selectionStart, end = el.selectionEnd;
    el.value = el.value.slice(0, start) + char + el.value.slice(end);
    el.selectionStart = el.selectionEnd = start + char.length;
    el.dispatchEvent(new Event('input', { bubbles: true }));
    if (touchKeyboardShift && key.length === 1) {
        touchKeyboardShift = false;
        var kb = document.getElementById('touchKeyboard');
        if (kb) {
            var shiftBtn = kb.querySelector('[data-key="Shift"]');
            if (shiftBtn) shiftBtn.classList.remove('active');
        }
    }
}

function showTouchKeyboard(el) {
    if (!isSleepModeHostErlaubt()) return;
    var kb = document.getElementById('touchKeyboard');
    if (!kb || !el) return;
    touchKeyboardActiveEl = el;
    if (el.readOnly !== undefined) el.readOnly = true;
    kb.classList.add('visible');
    kb.setAttribute('aria-hidden', 'false');
    el.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
}

function hideTouchKeyboard() {
    var kb = document.getElementById('touchKeyboard');
    if (!kb) return;
    kb.classList.remove('visible');
    kb.setAttribute('aria-hidden', 'true');
    if (touchKeyboardActiveEl && touchKeyboardActiveEl.readOnly !== undefined) {
        touchKeyboardActiveEl.readOnly = false;
    }
    touchKeyboardActiveEl = null;
    touchKeyboardShift = false;
    var shiftBtn = kb.querySelector('[data-key="Shift"]');
    if (shiftBtn) shiftBtn.classList.remove('active');
}

function initTouchKeyboard() {
    if (!isSleepModeHostErlaubt() || !isTouchDevice()) return;
    buildTouchKeyboard();
    var selector = 'input[type="text"], input[type="number"], textarea';
    document.querySelectorAll(selector).forEach(function(input) {
        input.addEventListener('focus', function() {
            showTouchKeyboard(input);
        });
        input.addEventListener('blur', function() {
            setTimeout(function() {
                if (document.activeElement !== input && !document.getElementById('touchKeyboard').contains(document.activeElement)) {
                    hideTouchKeyboard();
                }
            }, 150);
            if (input.readOnly !== undefined) input.readOnly = false;
        }, false);
    });
    document.getElementById('touchKeyboard').addEventListener('mousedown', function(e) { e.preventDefault(); });
}

function initZeitzoneDropdown() {
    var input = document.getElementById('settingZeitzone');
    var list = document.getElementById('zeitzoneList');
    if (!input || !list) return;

    if (list.children.length === 0) {
        ZEITZONEN.forEach(function(z) {
            var li = document.createElement('li');
            li.setAttribute('role', 'option');
            li.textContent = z;
            li.dataset.value = z;
            li.addEventListener('click', function() {
                input.value = z;
                list.setAttribute('aria-hidden', 'true');
                input.setAttribute('aria-expanded', 'false');
            });
            list.appendChild(li);
        });
    }

    function filterList() {
        var q = (input.value || '').toLowerCase();
        var items = list.querySelectorAll('li');
        items.forEach(function(li) {
            var show = !q || li.dataset.value.toLowerCase().indexOf(q) !== -1;
            li.classList.toggle('zeitzone-hidden', !show);
        });
        list.setAttribute('aria-hidden', 'false');
        input.setAttribute('aria-expanded', 'true');
    }

    function closeList() {
        zeitzoneListCloseTimer = setTimeout(function() {
            list.setAttribute('aria-hidden', 'true');
            input.setAttribute('aria-expanded', 'false');
        }, 200);
    }

    input.addEventListener('focus', filterList);
    input.addEventListener('input', filterList);
    input.addEventListener('keydown', function(e) {
        if (e.key === 'Escape') { list.setAttribute('aria-hidden', 'true'); input.setAttribute('aria-expanded', 'false'); }
    });
    input.addEventListener('blur', closeList);
    list.addEventListener('mousedown', function(e) { e.preventDefault(); });
}

function initPlatzhalterDragDrop() {
    var ta = document.getElementById('editSprachtext');
    if (!ta) return;
    document.querySelectorAll('.platzhalter-chip').forEach(function(chip) {
        chip.addEventListener('dragstart', function(e) {
            e.dataTransfer.setData('text/plain', chip.getAttribute('data-placeholder') || '');
            e.dataTransfer.effectAllowed = 'copy';
        });
    });
    ta.addEventListener('dragover', function(e) {
        e.preventDefault();
        e.dataTransfer.dropEffect = 'copy';
        ta.classList.add('editSprachtext-drop');
    });
    ta.addEventListener('dragleave', function(e) {
        if (!ta.contains(e.relatedTarget)) ta.classList.remove('editSprachtext-drop');
    });
    ta.addEventListener('drop', function(e) {
        e.preventDefault();
        ta.classList.remove('editSprachtext-drop');
        var text = e.dataTransfer.getData('text/plain');
        if (!text) return;
        var start = ta.selectionStart;
        var end = ta.selectionEnd;
        var val = ta.value;
        ta.value = val.slice(0, start) + text + val.slice(end);
        ta.selectionStart = ta.selectionEnd = start + text.length;
        ta.focus();
    });
}

function getTheme() {
    var t = document.documentElement.getAttribute('data-theme');
    return (t === 'light' || t === 'dark') ? t : 'light';
}

function setTheme(theme) {
    theme = theme === 'dark' ? 'dark' : 'light';
    document.documentElement.setAttribute('data-theme', theme);
    localStorage.setItem('wecker-theme', theme);
    var meta = document.getElementById('metaThemeColor');
    if (meta) meta.content = theme === 'dark' ? '#0d1117' : '#ffffff';
    updateThemeButtons(theme);
}
function updateThemeButtons(theme) {
    var icon = theme === 'dark' ? '/icon.php?name=theme-sun.svg' : '/icon.php?name=theme-moon.svg';
    var alt = theme === 'dark' ? 'Hell' : 'Dunkel';
    ['themeToggle', 'themeToggle4', 'themeToggle5', 'themeToggleMusik'].forEach(function(id) {
        var btn = document.getElementById(id);
        if (btn) {
            btn.innerHTML = '<img src="' + icon + '" alt="' + alt + '" class="theme-icon" width="22" height="22">';
        }
    });
}

function toggleTheme() {
    setTheme(getTheme() === 'dark' ? 'light' : 'dark');
}

(function updateThemeButton() {
    updateThemeButtons(getTheme());
})();

function loadAlbenListe() {
    var ul = document.getElementById('musikAlbenListe');
    var checkWrap = document.getElementById('musikPlaylistCheckboxes');
    if (!ul) return;
    ul.innerHTML = '';
    if (checkWrap) checkWrap.innerHTML = '';
    fetch(API_MUSIK + '?action=alben_list').then(function(r) { return r.json(); }).then(function(data) {
        if (!data.ok || !data.alben) return;
        window.musikPlaylists = data.alben;
        data.alben.forEach(function(a) {
            var li = document.createElement('li');
            var coverUrl = '/album_cover.php?album_id=' + a.id + '&t=' + Date.now();
            var hasIcon = a.icon ? '1' : '0';
            li.innerHTML = '<div class="album-cover-wrap"><img src="' + coverUrl + '" alt="" width="40" height="40" loading="lazy" onerror="this.style.display=\'none\'"></div>' +
                '<span>' + escapeHtml(a.name) + '</span>' +
                '<button type="button" class="header-btn" data-album-edit="' + a.id + '" data-album-name="' + escapeHtml(a.name) + '" data-album-has-icon="' + hasIcon + '" aria-label="Playlist bearbeiten">Bearbeiten</button>' +
                '<button type="button" class="header-btn" data-album-delete="' + a.id + '" aria-label="Playlist löschen">Löschen</button>';
            ul.appendChild(li);
            if (checkWrap) {
                var label = document.createElement('label');
                label.innerHTML = '<input type="checkbox" name="upload_playlist" value="' + a.id + '"> ' + escapeHtml(a.name);
                checkWrap.appendChild(label);
            }
        });
    });
}

function loadMusikListe() {
    var ul = document.getElementById('musikListe');
    var leer = document.getElementById('musikListeLeer');
    if (!ul) return;
    ul.innerHTML = '';
    if (leer) leer.style.display = 'none';
    fetch(API_MUSIK + '?action=list').then(function(r) { return r.json(); }).then(function(data) {
        if (!data.ok || !data.files || data.files.length === 0) {
            if (leer) leer.style.display = 'block';
            return;
        }
        if (!window.musikCoverPlaceholder) {
        window.musikCoverPlaceholder = 'data:image/svg+xml,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#888" stroke-width="1.5"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>');
    }
        data.files.forEach(function(f) {
            var li = document.createElement('li');
            var sizeStr = f.size >= 1048576 ? (f.size / 1048576).toFixed(1) + ' MB' : (f.size / 1024).toFixed(1) + ' KB';
            var displayName = (f.titel && f.titel.trim()) ? f.titel.trim() : f.name;
            var playlistStr = (f.playlists && String(f.playlists).trim()) ? escapeHtml(String(f.playlists).trim()) : '';
            var playlistIds = (f.playlist_ids && Array.isArray(f.playlist_ids)) ? f.playlist_ids.join(',') : '';
            var coverUrl = '/audio_cover.php?f=' + encodeURIComponent(f.name);
            var infoHtml = '<div class="musik-info"><span class="musik-titel" title="' + escapeHtml(f.name) + '">' + escapeHtml(displayName) + '</span>' +
                (playlistStr ? '<span class="musik-album">' + playlistStr + '</span>' : '') + '</div>';
            li.innerHTML = '<div class="musik-cover-wrap"><img src="' + coverUrl + '" alt="" class="musik-cover" loading="lazy" onerror="this.onerror=null;this.src=window.musikCoverPlaceholder"></div>' +
                infoHtml +
                '<span class="musik-size">' + sizeStr + '</span>' +
                '<audio src="' + escapeHtml(f.url) + '" controls preload="metadata" style="max-width:120px;height:32px;"></audio>' +
                '<button type="button" class="header-btn" data-musik-edit="' + f.id + '" data-musik-titel="' + escapeHtml(displayName) + '" data-musik-playlist-ids="' + escapeHtml(playlistIds) + '" aria-label="Bearbeiten">Bearbeiten</button>' +
                '<button type="button" class="header-btn" data-musik-delete="' + escapeHtml(f.name) + '" aria-label="Löschen">Löschen</button>';
            ul.appendChild(li);
        });
    }).catch(function() {
        if (leer) { leer.textContent = 'Fehler beim Laden.'; leer.style.display = 'block'; }
    });
}

function initMusik() {
    var uploadBtn = document.getElementById('musikUploadBtn');
    var fileInput = document.getElementById('musikFileInput');
    var statusEl = document.getElementById('musikUploadStatus');
    if (uploadBtn && fileInput) {
        uploadBtn.addEventListener('click', function() { fileInput.click(); });
        fileInput.addEventListener('change', function() {
            var files = fileInput.files;
            if (!files || files.length === 0) return;
            var checkWrap = document.getElementById('musikPlaylistCheckboxes');
            var playlistIds = [];
            if (checkWrap) {
                checkWrap.querySelectorAll('input[name="upload_playlist"]:checked').forEach(function(cb) { playlistIds.push(cb.value); });
            }
            var total = files.length;
            var done = 0;
            var failed = 0;
            function uploadNext(i) {
                if (i >= total) {
                    fileInput.value = '';
                    loadMusikListe();
                    if (statusEl) statusEl.textContent = failed ? done + ' hochgeladen, ' + failed + ' Fehler.' : (total === 1 ? 'Hochgeladen.' : total + ' Dateien hochgeladen.');
                    return;
                }
                if (statusEl) statusEl.textContent = 'Lade hoch … ' + (i + 1) + '/' + total;
                var fd = new FormData();
                fd.append('file', files[i]);
                fd.append('action', 'upload');
                playlistIds.forEach(function(pid) { fd.append('playlist_ids[]', pid); });
                fetch(API_MUSIK, { method: 'POST', body: fd })
                    .then(function(r) { return r.json(); })
                    .then(function(data) {
                        if (data.ok) done++; else failed++;
                        uploadNext(i + 1);
                    })
                    .catch(function() {
                        failed++;
                        uploadNext(i + 1);
                    });
            }
            uploadNext(0);
        });
    }
    var albumErstellenBtn = document.getElementById('musikAlbumErstellen');
    var albumNeuInput = document.getElementById('musikAlbumNeu');
    if (albumErstellenBtn && albumNeuInput) {
        albumErstellenBtn.addEventListener('click', function() {
            var name = albumNeuInput.value.trim();
            if (!name) return;
            var fd = new FormData();
            fd.append('action', 'alben_create');
            fd.append('name', name);
            fetch(API_MUSIK, { method: 'POST', body: fd }).then(function(r) { return r.json(); }).then(function(data) {
                if (data.ok) { albumNeuInput.value = ''; loadAlbenListe(); }
                else if (document.getElementById('musikUploadStatus')) document.getElementById('musikUploadStatus').textContent = data.error || 'Fehler';
            });
        });
    }
    var albenListe = document.getElementById('musikAlbenListe');
    if (albenListe) {
        albenListe.addEventListener('click', function(e) {
            var t = e.target;
            var editId = t && t.getAttribute('data-album-edit');
            var delId = t && t.getAttribute('data-album-delete');
            if (editId) {
                var overlay = document.getElementById('albumEditOverlay');
                var idInp = document.getElementById('albumEditId');
                var nameInp = document.getElementById('albumEditName');
                var preview = document.getElementById('albumEditPreview');
                var removeBtn = document.getElementById('albumEditIconRemove');
                var iconInput = document.getElementById('albumEditIconInput');
                if (overlay && idInp && nameInp && preview) {
                    idInp.value = editId;
                    nameInp.value = t.getAttribute('data-album-name') || '';
                    preview.src = '/album_cover.php?album_id=' + editId + '&t=' + Date.now();
                    preview.style.display = '';
                    if (removeBtn) removeBtn.style.display = (t.getAttribute('data-album-has-icon') === '1') ? '' : 'none';
                    if (iconInput) iconInput.value = '';
                    overlay.setAttribute('aria-hidden', 'false');
                }
                return;
            }
            if (delId) {
                if (!confirm('Playlist wirklich löschen? Die Songs bleiben erhalten und sind dann keiner Playlist zugeordnet.')) return;
                var fd = new FormData();
                fd.append('action', 'alben_delete');
                fd.append('id', delId);
                fetch(API_MUSIK, { method: 'POST', body: fd }).then(function(r) { return r.json(); }).then(function(data) {
                    if (data.ok) { loadAlbenListe(); loadMusikListe(); }
                });
            }
        });
    }
    var albumEditCancel = document.getElementById('albumEditCancel');
    var albumEditSave = document.getElementById('albumEditSave');
    var albumEditOverlay = document.getElementById('albumEditOverlay');
    var albumEditIconBtn = document.getElementById('albumEditIconBtn');
    var albumEditIconInput = document.getElementById('albumEditIconInput');
    var albumEditIconRemove = document.getElementById('albumEditIconRemove');
    if (albumEditCancel && albumEditOverlay) albumEditCancel.addEventListener('click', function() { albumEditOverlay.setAttribute('aria-hidden', 'true'); });
    if (albumEditIconBtn && albumEditIconInput) albumEditIconBtn.addEventListener('click', function() { albumEditIconInput.click(); });
    if (albumEditIconInput) albumEditIconInput.addEventListener('change', function() {
        var file = albumEditIconInput.files && albumEditIconInput.files[0];
        if (file && window.URL && URL.createObjectURL) {
            var prev = document.getElementById('albumEditPreview');
            if (prev) { prev.src = URL.createObjectURL(file); prev.style.display = ''; }
        }
    });
    if (albumEditIconRemove) albumEditIconRemove.addEventListener('click', function() {
        var idInp = document.getElementById('albumEditId');
        if (!idInp || !idInp.value) return;
        var fd = new FormData();
        fd.append('action', 'alben_icon_remove');
        fd.append('id', idInp.value);
        fetch(API_MUSIK, { method: 'POST', body: fd }).then(function(r) { return r.json(); }).then(function(data) {
            if (data.ok) {
                var prev = document.getElementById('albumEditPreview');
                if (prev) { prev.src = '/album_cover.php?album_id=' + idInp.value + '&t=' + Date.now(); }
                albumEditIconRemove.style.display = 'none';
                loadAlbenListe();
            }
        });
    });
    if (albumEditSave && albumEditOverlay) albumEditSave.addEventListener('click', function() {
        var idInp = document.getElementById('albumEditId');
        var nameInp = document.getElementById('albumEditName');
        var iconInput = document.getElementById('albumEditIconInput');
        if (!idInp || !idInp.value) return;
        var fd = new FormData();
        fd.append('action', 'alben_update');
        fd.append('id', idInp.value);
        fd.append('name', nameInp ? nameInp.value.trim() : '');
        if (iconInput && iconInput.files && iconInput.files[0]) fd.append('icon', iconInput.files[0]);
        fetch(API_MUSIK, { method: 'POST', body: fd }).then(function(r) { return r.json(); }).then(function(data) {
            if (data.ok) { albumEditOverlay.setAttribute('aria-hidden', 'true'); loadAlbenListe(); loadMusikListe(); if (iconInput) iconInput.value = ''; }
        });
    });
    var liste = document.getElementById('musikListe');
    if (liste) {
        liste.addEventListener('click', function(e) {
            var t = e.target;
            var del = t && t.getAttribute('data-musik-delete');
            var editId = t && t.getAttribute('data-musik-edit');
            if (del) {
                if (!confirm('Datei „' + del + '“ wirklich löschen?')) return;
                var fd = new FormData();
                fd.append('action', 'delete');
                fd.append('name', del);
                fetch(API_MUSIK, { method: 'POST', body: fd })
                    .then(function(r) { return r.json(); })
                    .then(function(data) { if (data.ok) loadMusikListe(); });
                return;
            }
            if (editId) {
                var editOverlay = document.getElementById('musikEditOverlay');
                var idInp = document.getElementById('musikEditId');
                var titelInp = document.getElementById('musikEditTitel');
                var playlistsWrap = document.getElementById('musikEditPlaylists');
                if (editOverlay && idInp && titelInp) {
                    idInp.value = editId;
                    titelInp.value = t.getAttribute('data-musik-titel') || '';
                    var idsStr = t.getAttribute('data-musik-playlist-ids') || '';
                    var checkedIds = idsStr ? idsStr.split(',').map(function(x) { return x.trim(); }) : [];
                    if (playlistsWrap && window.musikPlaylists && Array.isArray(window.musikPlaylists)) {
                        playlistsWrap.innerHTML = '';
                        window.musikPlaylists.forEach(function(p) {
                            var label = document.createElement('label');
                            var checked = checkedIds.indexOf(String(p.id)) !== -1;
                            label.innerHTML = '<input type="checkbox" name="edit_playlist" value="' + p.id + '"' + (checked ? ' checked' : '') + '> ' + escapeHtml(p.name);
                            playlistsWrap.appendChild(label);
                        });
                    }
                    editOverlay.setAttribute('aria-hidden', 'false');
                }
            }
        });
    }
    var editOverlay = document.getElementById('musikEditOverlay');
    var editCancel = document.getElementById('musikEditCancel');
    var editSave = document.getElementById('musikEditSave');
    if (editCancel && editOverlay) editCancel.addEventListener('click', function() { editOverlay.setAttribute('aria-hidden', 'true'); });
    if (editSave && editOverlay) {
        editSave.addEventListener('click', function() {
            var idInp = document.getElementById('musikEditId');
            var titelInp = document.getElementById('musikEditTitel');
            var playlistsWrap = document.getElementById('musikEditPlaylists');
            if (!idInp || !titelInp) return;
            var fd = new FormData();
            fd.append('action', 'update');
            fd.append('id', idInp.value);
            fd.append('titel', titelInp.value.trim());
            if (playlistsWrap) {
                playlistsWrap.querySelectorAll('input[name="edit_playlist"]:checked').forEach(function(cb) {
                    fd.append('playlist_ids[]', cb.value);
                });
            }
            fetch(API_MUSIK, { method: 'POST', body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    if (data.ok) { editOverlay.setAttribute('aria-hidden', 'true'); loadMusikListe(); }
                });
        });
    }
}

var VALID_PANELS = ['panelAlarm', 'panelTimer', 'panelWetter', 'panelMusik'];
function showPanel(panelId) {
    if (VALID_PANELS.indexOf(panelId) === -1) return;
    document.querySelectorAll('.panel').forEach(function(p) { p.classList.remove('active'); });
    document.querySelectorAll('.bottom-nav button[data-panel]').forEach(function(b) { b.classList.remove('active'); });
    var p = document.getElementById(panelId);
    var btn = document.querySelector('.bottom-nav button[data-panel="' + panelId + '"]');
    if (p) p.classList.add('active');
    if (btn) btn.classList.add('active');
    localStorage.setItem('wecker-active-tab', panelId);
    if (panelId === 'panelWetter') loadWetterFromStorage();
    if (panelId === 'panelMusik') { loadAlbenListe(); loadMusikListe(); }
}

function applyAccentColor(hex) {
    var root = document.documentElement;
    if (!hex || hex === '') {
        root.style.removeProperty('--accent');
        try { localStorage.removeItem('wecker-accent'); } catch (e) {}
        return;
    }
    root.style.setProperty('--accent', hex);
    try { localStorage.setItem('wecker-accent', hex); } catch (e) {}
}

document.addEventListener('DOMContentLoaded', function() {
    var accent = null;
    try { accent = localStorage.getItem('wecker-accent'); } catch (e) {}
    if (accent) applyAccentColor(accent);

    var loading = document.getElementById('loadingScreen');
    if (loading) {
        loading.classList.add('hidden');
        setTimeout(function() { loading.remove(); }, 400);
    }
    initZeitzoneDropdown();
    var btnTheme = document.getElementById('themeToggle');
    if (btnTheme) btnTheme.addEventListener('click', toggleTheme);
    ['themeToggle4','themeToggle5','themeToggleMusik'].forEach(function(id) {
        var b = document.getElementById(id);
        if (b) b.addEventListener('click', toggleTheme);
    });
    var btnNeu = document.getElementById('btnNeuWecker');
    if (btnNeu) btnNeu.addEventListener('click', neuWecker);
    document.querySelectorAll('.btn-open-settings').forEach(function(btn) {
        btn.addEventListener('click', openSettings);
    });

    document.querySelectorAll('.settings-tabs button[data-tab]').forEach(function(btn) {
        btn.addEventListener('click', function() {
            showSettingsTab(btn.getAttribute('data-tab'));
            if (btn.getAttribute('data-tab') === 'settingsTabUpdate') loadUpdateTab();
        });
    });

    document.querySelectorAll('.bottom-nav button[data-panel]').forEach(function(btn) {
        btn.addEventListener('click', function() { showPanel(btn.getAttribute('data-panel')); });
    });

    var savedTab = localStorage.getItem('wecker-active-tab');
    if (savedTab && VALID_PANELS.indexOf(savedTab) !== -1 && document.getElementById(savedTab)) {
        showPanel(savedTab);
    }

    initTimer();
    initWetter();
    initMusik();
    initUpdateTab();
    initSleepMode();
    initPlatzhalterDragDrop();
    initTouchKeyboard();

    setTimeout(function() { checkForUpdateNotification(); }, 1500);

    var updateToastToSettings = document.getElementById('updateToastToSettings');
    var updateToastClose = document.getElementById('updateToastClose');
    if (updateToastToSettings) {
        updateToastToSettings.addEventListener('click', function() {
            hideUpdateToast();
            openSettings();
            showSettingsTab('settingsTabUpdate');
            loadUpdateTab();
        });
    }
    if (updateToastClose) updateToastClose.addEventListener('click', hideUpdateToast);
});

function checkForUpdateNotification() {
    Promise.all([
        fetch(API_UPDATE + '?action=current').then(function(r) { return r.json(); }),
        fetch(API_UPDATE + '?action=list').then(function(r) { return r.json(); })
    ]).then(function(results) {
        var current = results[0];
        var list = results[1];
        if (!list || !list.ok || !list.versionen || list.versionen.length === 0) return;
        var latest = list.versionen[0];
        var latestVer = latest && (latest.version || '');
        var currentVer = (current && current.ok && current.version != null) ? String(current.version) : '0.0.0';
        if (latestVer && latestVer !== currentVer) {
            showUpdateToast();
        }
    }).catch(function() {});
}

function showUpdateToast() {
    var toast = document.getElementById('updateToast');
    if (!toast) return;
    toast.style.display = 'block';
}

function hideUpdateToast() {
    var toast = document.getElementById('updateToast');
    if (toast) toast.style.display = 'none';
}

function renderAudioLists() {
    ['vorher', 'gleichzeitig', 'danach'].forEach(function(pos) {
        const ul = document.getElementById('audioList' + pos.charAt(0).toUpperCase() + pos.slice(1));
        ul.innerHTML = '';
        audioList.filter(function(a) { return a.position === pos; }).forEach(function(a, i) {
            const idx = audioList.indexOf(a);
            const li = document.createElement('li');
            li.innerHTML = '<span title="' + escapeHtml(a.datei) + '">' + escapeHtml(a.titel || a.datei) + '</span><button type="button" onclick="removeAudio(' + idx + ')">Entfernen</button>';
            ul.appendChild(li);
        });
    });
    document.getElementById('editAudio').value = JSON.stringify(audioList);
}

function addAudio(position) {
    const pfadId = 'audioPfad' + position.charAt(0).toUpperCase() + position.slice(1);
    const titelId = 'audioTitel' + position.charAt(0).toUpperCase() + position.slice(1);
    const pfad = (document.getElementById(pfadId).value || '').trim();
    if (!pfad) return;
    audioList.push({ datei: pfad, titel: (document.getElementById(titelId).value || '').trim() || null, position: position });
    document.getElementById(pfadId).value = '';
    document.getElementById(titelId).value = '';
    renderAudioLists();
}

function removeAudio(index) {
    audioList.splice(index, 1);
    renderAudioLists();
}

function getNextAlarm() {
    const now = new Date();
    let next = null;
    const tag = now.getDay() || 7;
    const heute = now.toISOString().slice(0, 10);
    weckerListeData.forEach(function(w) {
        if (!w.aktiv) return;
        const [h, m] = (w.weckzeit || '00:00').split(':').map(Number);
        if (w.wiederholung === 'einmal') {
            if (!w.weckdatum) return;
            const d = new Date(w.weckdatum + 'T' + (h < 10 ? '0' : '') + h + ':' + (m < 10 ? '0' : '') + m + ':00');
            if (d > now && (!next || d < next.dateTime)) next = { dateTime: d, label: w.sprachtext || w.weckzeit };
        } else if (w.wiederholung === 'woechentlich' && w.wochentage && w.wochentage.length) {
            for (let i = 0; i <= 7; i++) {
                const d = new Date(now);
                d.setDate(d.getDate() + i);
                const t = d.getDay() || 7;
                if (w.wochentage.indexOf(t) === -1) continue;
                d.setHours(h, m, 0, 0);
                if (d > now && (!next || d < next.dateTime)) next = { dateTime: d, label: w.sprachtext || w.weckzeit };
            }
        } else {
            const d = new Date(now);
            d.setHours(h, m, 0, 0);
            if (d <= now) d.setDate(d.getDate() + 1);
            if (!next || d < next.dateTime) next = { dateTime: d, label: w.sprachtext || w.weckzeit };
        }
    });
    return next;
}

function getNextAlarms(limit) {
    const now = new Date();
    const candidates = [];
    weckerListeData.forEach(function(w) {
        if (!w.aktiv) return;
        const [h, m] = (w.weckzeit || '00:00').split(':').map(Number);
        const label = w.sprachtext || (w.weckzeit || '00:00').substring(0, 5);
        const meta = getMeta(w);
        if (w.wiederholung === 'einmal' && w.weckdatum) {
            const d = new Date(w.weckdatum + 'T' + (h < 10 ? '0' : '') + h + ':' + (m < 10 ? '0' : '') + m + ':00');
            if (d > now) candidates.push({ dateTime: d, label: label, meta: meta });
        } else if (w.wiederholung === 'woechentlich' && w.wochentage && w.wochentage.length) {
            for (let i = 0; i <= 7; i++) {
                const d = new Date(now);
                d.setDate(d.getDate() + i);
                const t = d.getDay() || 7;
                if (w.wochentage.indexOf(t) === -1) continue;
                d.setHours(h, m, 0, 0);
                if (d > now) candidates.push({ dateTime: d, label: label, meta: meta });
            }
        } else {
            const d = new Date(now);
            d.setHours(h, m, 0, 0);
            if (d <= now) d.setDate(d.getDate() + 1);
            candidates.push({ dateTime: d, label: label, meta: meta });
        }
    });
    candidates.sort(function(a, b) { return a.dateTime - b.dateTime; });
    var seen = {};
    return candidates.filter(function(a) {
        var key = a.dateTime.getTime();
        if (seen[key]) return false;
        seen[key] = true;
        return true;
    }).slice(0, limit || 5);
}

function updateHeader() {
    const next = getNextAlarm();
    const titleEl = document.getElementById('headerTitle');
    const subEl = document.getElementById('headerSubtitle');
    if (!next) {
        titleEl.textContent = 'Kein Alarm';
        subEl.textContent = '–';
        return;
    }
    const min = Math.round((next.dateTime - new Date()) / 60000);
    if (min < 0) { updateHeader(); return; }
    if (min < 60) titleEl.textContent = 'Alarm in ' + min + ' Minute' + (min !== 1 ? 'n' : '');
    else if (min < 1440) titleEl.textContent = 'Alarm in ' + Math.round(min / 60) + ' Stunde' + (Math.round(min / 60) !== 1 ? 'n' : '');
    else titleEl.textContent = 'Alarm in ' + Math.round(min / 1440) + ' Tag' + (Math.round(min / 1440) !== 1 ? 'en' : '');
    const opts = { weekday: 'short', day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' };
    subEl.textContent = next.dateTime.toLocaleDateString('de-DE', opts);
}
updateHeader();
setInterval(updateHeader, 60000);

function getMeta(w) {
    if (w.wiederholung === 'woechentlich' && w.wochentage && w.wochentage.length) {
        return w.wochentage.map(function(t) { return WOChentAGE_LABELS[t] || ''; }).join(' ');
    }
    if (w.wiederholung === 'einmal' && w.weckdatum) {
        var d = new Date(w.weckdatum + 'T12:00:00');
        if (!isNaN(d.getTime())) return d.toLocaleDateString('de-DE', { weekday: 'short', day: 'numeric', month: 'long' });
        return '';
    }
    return 'Täglich';
}

function renderWeckerListe(list) {
    var el = document.getElementById('weckerListe');
    if (!el) return;
    var html = '';
    (list || []).forEach(function(w) {
        var meta = getMeta(w);
        var label = (w.sprachtext || '').length > 25 ? (w.sprachtext || '').substring(0, 22) + '…' : (w.sprachtext || '');
        var aktiv = !!w.aktiv;
        html += '<div class="wecker ' + (aktiv ? '' : 'inaktiv') + '" data-id="' + w.id + '" onclick="editWecker(' + w.id + ')">';
        html += '<div class="wecker-left">';
        if (label) html += '<span class="wecker-label">' + escapeHtml(label) + '</span>';
        html += '<div class="weckzeit">' + escapeHtml((w.weckzeit || '00:00').substring(0, 5)) + '</div>';
        html += '<div class="wecker-meta">' + escapeHtml(meta) + '</div></div>';
        html += '<label class="toggle" onclick="event.stopPropagation()">';
        html += '<input type="checkbox" ' + (aktiv ? 'checked' : '') + ' onchange="toggleWecker(' + w.id + ', this.checked)"><span></span></label></div>';
    });
    el.innerHTML = html;
}

function refreshWeckerData() {
    fetch(API_WECKER + '?action=list').then(function(r) { return r.json(); }).then(function(data) {
        if (!data.ok) return;
        weckerListeData = data.wecker || [];
        renderWeckerListe(weckerListeData);
        updateHeader();
    }).catch(function() {});
}
setInterval(refreshWeckerData, 30000);

function toggleWochentageVisibility() {
    const w = document.getElementById('editWiederholung').value;
    document.getElementById('rowWochentage').style.display = w === 'woechentlich' ? 'block' : 'none';
    document.getElementById('rowWeckdatum').style.display = w === 'einmal' ? 'block' : 'none';
}

function editWecker(id) {
    var overlay = document.getElementById('editOverlay');
    var loading = document.getElementById('editOverlayLoading');
    var content = document.getElementById('editBoxContent');
    if (loading) loading.style.display = 'flex';
    if (content) content.style.display = 'none';
    overlay.style.display = 'flex';
    fetch(API_WECKER + '?action=get&id=' + encodeURIComponent(id))
        .then(r => r.json())
        .then(data => {
            if (loading) loading.style.display = 'none';
            if (content) content.style.display = 'block';
            if (!data.ok) { alert(data.error || 'Fehler'); closeEdit(); return; }
            const w = data.wecker;
            document.getElementById('editTitle').textContent = 'Wecker bearbeiten';
            document.getElementById('editId').value = w.id;
            document.getElementById('editWeckzeit').value = (w.weckzeit || '').substring(0, 5);
            document.getElementById('editWeckdatum').value = w.weckdatum || '';
            document.getElementById('editWiederholung').value = w.wiederholung || 'taeglich';
            document.getElementById('editSprachtext').value = w.sprachtext || '';
            const wochentage = w.wochentage || [];
            document.querySelectorAll('#editForm input[name="wochentage[]"]').forEach(cb => {
                cb.checked = wochentage.indexOf(parseInt(cb.value, 10)) !== -1;
            });
            toggleWochentageVisibility();
            audioList = Array.isArray(w.audio) ? w.audio.map(function(a) { return { datei: a.datei, titel: a.titel || null, position: a.position || 'danach' }; }) : [];
            renderAudioLists();
            document.getElementById('btnDeleteWecker').style.display = '';
            document.getElementById('editOverlay').style.display = 'flex';
        })
        .catch(function() {
            if (loading) loading.style.display = 'none';
            if (content) content.style.display = 'block';
            closeEdit();
            alert('Fehler beim Laden');
        });
}

function neuWecker() {
    document.getElementById('editOverlayLoading').style.display = 'none';
    document.getElementById('editBoxContent').style.display = 'block';
    document.getElementById('editTitle').textContent = 'Neuer Wecker';
    document.getElementById('editId').value = '0';
    document.getElementById('editWeckzeit').value = '07:00';
    document.getElementById('editWeckdatum').value = '';
    document.getElementById('editWiederholung').value = 'taeglich';
    document.getElementById('editSprachtext').value = '';
    document.querySelectorAll('#editForm input[name="wochentage[]"]').forEach(cb => { cb.checked = false; });
    audioList = [];
    renderAudioLists();
    toggleWochentageVisibility();
    document.getElementById('btnDeleteWecker').style.display = 'none';
    document.getElementById('editOverlay').style.display = 'flex';
}

function closeEdit() {
    hideTouchKeyboard();
    document.getElementById('editOverlay').style.display = 'none';
}

function toggleWecker(id, aktiv) {
    const card = document.querySelector('.wecker[data-id="' + id + '"]');
    const checkbox = card ? card.querySelector('input[type="checkbox"]') : null;
    const prev = !aktiv;
    if (card) card.classList.toggle('inaktiv', !aktiv);
    fetch(API_WECKER + '?action=toggle&id=' + encodeURIComponent(id) + '&aktiv=' + (aktiv ? '1' : '0'))
        .then(r => r.json())
        .then(data => {
            if (!data.ok && checkbox) { checkbox.checked = prev; if (card) card.classList.toggle('inaktiv', prev); alert(data.error || 'Fehler'); }
            else if (data.ok) updateHeader();
        })
        .catch(() => { if (checkbox) checkbox.checked = prev; if (card) card.classList.toggle('inaktiv', prev); alert('Netzwerkfehler'); });
}

function saveWecker(e) {
    e.preventDefault();
    document.getElementById('editAudio').value = JSON.stringify(audioList);
    const form = document.getElementById('editForm');
    const fd = new FormData(form);
    fd.set('action', 'save');
    fetch(API_WECKER, { method: 'POST', body: fd })
        .then(r => r.json())
        .then(data => {
            if (!data.ok) { alert(data.error || 'Fehler'); return; }
            closeEdit();
            location.reload();
        })
        .catch(() => alert('Fehler beim Speichern'));
    return false;
}

function deleteWecker() {
    const id = document.getElementById('editId').value;
    if (!id || id === '0') return;
    if (!confirm('Wecker wirklich löschen?')) return;
    const fd = new FormData();
    fd.set('action', 'delete');
    fd.set('id', id);
    fetch(API_WECKER, { method: 'POST', body: fd })
        .then(r => r.json())
        .then(data => {
            if (!data.ok) { alert(data.error || 'Fehler'); return; }
            closeEdit();
            location.reload();
        })
        .catch(() => alert('Fehler beim Löschen'));
}

function openSettings() {
    var overlay = document.getElementById('settingsOverlay');
    var loading = document.getElementById('settingsOverlayLoading');
    overlay.style.display = 'flex';
    if (loading) loading.style.display = 'flex';
    document.getElementById('settingsMsg').textContent = '';
    showSettingsTab('settingsTabAllgemein');
    fetch(API_EINSTELLUNGEN)
        .then(r => r.json())
        .then(data => {
            if (loading) loading.style.display = 'none';
            if (!data.ok) return;
            const e = data.einstellungen || {};
            document.getElementById('settingTtsName').value = e.tts_name || '';
            document.getElementById('settingTtsModell').value = e.tts_modell || 'de_DE-thorsten-high';
            document.getElementById('settingLautstaerke').value = e.lautstaerke || '100';
            document.getElementById('settingLautstaerkeVal').textContent = e.lautstaerke || '100';
            document.getElementById('settingLautstaerkeMusik').value = e.lautstaerke_musik || '80';
            document.getElementById('settingLautstaerkeMusikVal').textContent = e.lautstaerke_musik || '80';
            document.getElementById('settingMusikDucking').value = e.musik_ducking_prozent || '20';
            document.getElementById('settingMusikDuckingVal').textContent = e.musik_ducking_prozent || '20';
            document.getElementById('settingZeitzone').value = e.zeitzone || 'Europe/Berlin';
            document.getElementById('settingWetterOrt').value = e.wetter_ort || '';
            document.getElementById('settingSleepModeSekunden').value = e.sleep_mode_sekunden !== undefined && e.sleep_mode_sekunden !== '' ? e.sleep_mode_sekunden : '0';
            var accent = (e.accent_color || '').trim();
            document.getElementById('settingAccentColor').value = accent && /^#[0-9a-fA-F]{3,6}$/.test(accent) ? accent : '#2563eb';
            document.getElementById('settingAccentColorHex').value = accent || '';
        })
        .catch(function() {
            if (loading) loading.style.display = 'none';
            document.getElementById('settingsMsg').textContent = 'Fehler beim Laden.';
            document.getElementById('settingsMsg').className = 'settings-msg error';
        });
    loadTtsCustomList();
}

function showSettingsTab(tabId) {
    document.querySelectorAll('.settings-tab-panel').forEach(function(p) { p.classList.remove('active'); });
    document.querySelectorAll('.settings-tabs button[role="tab"]').forEach(function(b) {
        b.classList.remove('active');
        b.setAttribute('aria-selected', b.getAttribute('data-tab') === tabId ? 'true' : 'false');
    });
    var panel = document.getElementById(tabId);
    var btn = document.querySelector('.settings-tabs button[data-tab="' + tabId + '"]');
    if (panel) panel.classList.add('active');
    if (btn) btn.classList.add('active');
}

function closeSettings(ev) {
    if (ev && ev.target !== ev.currentTarget) return;
    hideTouchKeyboard();
    document.getElementById('settingsOverlay').style.display = 'none';
}

document.getElementById('settingLautstaerke').addEventListener('input', function() {
    document.getElementById('settingLautstaerkeVal').textContent = this.value;
});

(function initAccentColor() {
    var colorEl = document.getElementById('settingAccentColor');
    var hexEl = document.getElementById('settingAccentColorHex');
    var resetEl = document.getElementById('settingAccentReset');
    if (!colorEl || !hexEl) return;
    colorEl.addEventListener('input', function() {
        var hex = this.value;
        hexEl.value = hex;
        applyAccentColor(hex);
    });
    hexEl.addEventListener('input', function() {
        var v = this.value.trim();
        if (v === '') { applyAccentColor(''); return; }
        if (/^#[0-9a-fA-F]{3,6}$/.test(v)) {
            colorEl.value = v.length === 4 ? '#' + v[1] + v[1] + v[2] + v[2] + v[3] + v[3] : v;
            applyAccentColor(v);
        }
    });
    if (resetEl) resetEl.addEventListener('click', function() {
        colorEl.value = '#2563eb';
        hexEl.value = '';
        applyAccentColor('');
    });
})();

function loadTtsCustomList() {
    fetch(API_WECKER + '?action=tts_custom_list')
        .then(r => r.json())
        .then(data => {
            const list = document.getElementById('ttsCustomList');
            list.innerHTML = '';
            if (!data.ok || !data.items || data.items.length === 0) {
                list.innerHTML = '<p class="platzhalter-hint">Keine benutzerdefinierten Platzhalter.</p>';
                return;
            }
            data.items.forEach(item => {
                const div = document.createElement('div');
                div.className = 'tts-custom-item';
                div.innerHTML = '<span><code>' + escapeHtml(item.platzhalter) + '</code> → ' + escapeHtml(item.ersetzungstext) + '</span>' +
                    '<div><button type="button" class="header-btn btn-small" onclick="editCustom(' + item.id + ')">Bearb.</button> ' +
                    '<button type="button" class="header-btn btn-small danger" onclick="deleteCustom(' + item.id + ')">Löschen</button></div>';
                list.appendChild(div);
            });
        });
}

function escapeHtml(s) {
    const d = document.createElement('div');
    d.textContent = s;
    return d.innerHTML;
}

function editCustom(id) {
    fetch(API_WECKER + '?action=tts_custom_list')
        .then(r => r.json())
        .then(data => {
            if (!data.ok || !data.items) return;
            const item = data.items.find(i => parseInt(i.id, 10) === id);
            if (!item) return;
            document.getElementById('customId').value = item.id;
            document.getElementById('customPlatzhalter').value = (item.platzhalter || '').replace(/^\{\{|\}\}$/g, '');
            document.getElementById('customErsetzungstext').value = item.ersetzungstext || '';
            document.getElementById('customBeschreibung').value = item.beschreibung || '';
            document.getElementById('btnCustomSave').textContent = 'Speichern';
        });
}

function deleteCustom(id) {
    if (!confirm('Platzhalter löschen?')) return;
    const fd = new FormData();
    fd.set('action', 'tts_custom_delete');
    fd.set('id', id);
    fetch(API_WECKER, { method: 'POST', body: fd })
        .then(r => r.json())
        .then(data => {
            if (data.ok) loadTtsCustomList();
            else document.getElementById('settingsMsg').textContent = data.error || 'Fehler';
            document.getElementById('settingsMsg').className = 'settings-msg' + (data.ok ? '' : ' error');
        });
}

document.getElementById('btnCustomSave').addEventListener('click', function() {
    const id = document.getElementById('customId').value;
    const platzhalter = (document.getElementById('customPlatzhalter').value || '').trim();
    const ersetzungstext = (document.getElementById('customErsetzungstext').value || '').trim();
    const beschreibung = (document.getElementById('customBeschreibung').value || '').trim();
    if (!platzhalter) { document.getElementById('settingsMsg').textContent = 'Platzhalter eingeben'; document.getElementById('settingsMsg').className = 'settings-msg error'; return; }
    const fd = new FormData();
    fd.set('action', 'tts_custom_save');
    if (id && id !== '0') fd.set('id', id);
    fd.set('platzhalter', platzhalter);
    fd.set('ersetzungstext', ersetzungstext);
    fd.set('beschreibung', beschreibung);
    fd.set('aktiv', '1');
    fetch(API_WECKER, { method: 'POST', body: fd })
        .then(r => r.json())
        .then(data => {
            if (!data.ok) { document.getElementById('settingsMsg').textContent = data.error || 'Fehler'; document.getElementById('settingsMsg').className = 'settings-msg error'; return; }
            document.getElementById('customId').value = '0';
            document.getElementById('customPlatzhalter').value = '';
            document.getElementById('customErsetzungstext').value = '';
            document.getElementById('customBeschreibung').value = '';
            document.getElementById('btnCustomSave').textContent = 'Hinzufügen / Speichern';
            document.getElementById('settingsMsg').textContent = 'Gespeichert.';
            document.getElementById('settingsMsg').className = 'settings-msg';
            loadTtsCustomList();
        });
});

function saveSettings() {
    const fd = new FormData();
    fd.set('tts_name', document.getElementById('settingTtsName').value);
    fd.set('tts_modell', document.getElementById('settingTtsModell').value);
    fd.set('lautstaerke', document.getElementById('settingLautstaerke').value);
    fd.set('lautstaerke_musik', document.getElementById('settingLautstaerkeMusik').value);
    fd.set('musik_ducking_prozent', document.getElementById('settingMusikDucking').value);
    fd.set('zeitzone', document.getElementById('settingZeitzone').value);
    fd.set('wetter_ort', document.getElementById('settingWetterOrt').value);
    fd.set('sleep_mode_sekunden', document.getElementById('settingSleepModeSekunden').value);
    var accentVal = (document.getElementById('settingAccentColorHex').value || '').trim();
    if (!/^#[0-9a-fA-F]{3,6}$/.test(accentVal)) accentVal = '';
    fd.set('accent_color', accentVal);
    fetch(API_EINSTELLUNGEN, { method: 'POST', body: fd })
        .then(r => r.json())
        .then(data => {
            const msg = document.getElementById('settingsMsg');
            msg.textContent = data.ok ? 'Einstellungen gespeichert.' : (data.error || 'Fehler');
            msg.className = 'settings-msg' + (data.ok ? '' : ' error');
            if (data.ok) {
                var v = parseInt(document.getElementById('settingSleepModeSekunden').value, 10);
                sleepModeSekunden = isNaN(v) ? 0 : Math.max(0, Math.min(86400, v));
                var ac = (document.getElementById('settingAccentColorHex').value || '').trim();
                applyAccentColor(/^#[0-9a-fA-F]{3,6}$/.test(ac) ? ac : '');
            }
        })
        .catch(() => {
            document.getElementById('settingsMsg').textContent = 'Netzwerkfehler';
            document.getElementById('settingsMsg').className = 'settings-msg error';
        });
}

var timerInterval = null, timerRest = 0;

function getTimerPresets() {
    try {
        var raw = localStorage.getItem('wecker-timer-presets');
        if (raw) {
            var arr = JSON.parse(raw);
            if (Array.isArray(arr) && arr.length >= 4) return arr.slice(0, 4).map(function(n) { return Math.max(1, Math.min(99, parseInt(n, 10) || 15)); });
        }
    } catch (e) {}
    return [15, 30, 45, 60];
}

function setTimerPresets(arr) {
    var four = (arr || []).slice(0, 4).map(function(n) { return Math.max(1, Math.min(99, parseInt(n, 10) || 15)); });
    while (four.length < 4) four.push([15, 30, 45, 60][four.length]);
    try { localStorage.setItem('wecker-timer-presets', JSON.stringify(four)); } catch (e) {}
    return four;
}

function initTimer() {
    var inpMin = document.getElementById('timerMin');
    var inpSek = document.getElementById('timerSek');
    var anzeige = document.getElementById('timerAnzeige');
    var btnStart = document.getElementById('timerStart');
    var btnStop = document.getElementById('timerStop');
    var setDiv = document.querySelector('.timer-set');
    var presetsContainer = document.getElementById('timerPresets');
    var editOverlay = document.getElementById('timerPresetEditOverlay');
    var editMin = document.getElementById('timerPresetEditMin');
    var editSave = document.getElementById('timerPresetEditSave');
    var editCancel = document.getElementById('timerPresetEditCancel');

    function showSet(show) { if (setDiv) setDiv.style.display = show ? 'flex' : 'none'; if (anzeige) anzeige.style.display = show ? 'none' : 'block'; if (btnStop) btnStop.style.display = show ? 'none' : 'inline-flex'; }

    function renderPresets() {
        if (!presetsContainer) return;
        var presets = getTimerPresets();
        presetsContainer.innerHTML = '';
        presets.forEach(function(minutes, index) {
            var btn = document.createElement('button');
            btn.type = 'button';
            btn.className = 'timer-preset-btn';
            btn.textContent = minutes + ' Min';
            btn.dataset.minutes = minutes;
            btn.dataset.index = index;
            btn.setAttribute('aria-label', minutes + ' Minuten. Lang drücken zum Bearbeiten.');
            var longPressTimer = null;
            function clearLongPress() {
                if (longPressTimer) { clearTimeout(longPressTimer); longPressTimer = null; }
            }
            btn.addEventListener('click', function(e) {
                if (longPressTimer) return;
                if (editOverlay && editOverlay.classList.contains('show')) return;
                if (timerInterval) return;
                inpMin.value = minutes;
                if (inpSek) inpSek.value = 0;
            });
            btn.addEventListener('contextmenu', function(e) {
                e.preventDefault();
                openPresetEdit(index);
            });
            btn.addEventListener('touchstart', function(e) {
                clearLongPress();
                longPressTimer = setTimeout(function() {
                    longPressTimer = null;
                    e.preventDefault();
                    openPresetEdit(index);
                }, 500);
            });
            btn.addEventListener('touchend', clearLongPress);
            btn.addEventListener('touchcancel', clearLongPress);
            presetsContainer.appendChild(btn);
        });
    }

    var editingPresetIndex = -1;
    function openPresetEdit(index) {
        var presets = getTimerPresets();
        if (index < 0 || index >= presets.length) return;
        editingPresetIndex = index;
        if (editMin) editMin.value = presets[index];
        if (editOverlay) editOverlay.classList.add('show');
    }
    function closePresetEdit() {
        editingPresetIndex = -1;
        if (editOverlay) editOverlay.classList.remove('show');
    }
    if (editOverlay) {
        editOverlay.addEventListener('click', function(e) { if (e.target === editOverlay) closePresetEdit(); });
    }
    if (editCancel) editCancel.addEventListener('click', closePresetEdit);
    if (editSave && editMin) editSave.addEventListener('click', function() {
        var val = Math.max(1, Math.min(99, parseInt(editMin.value, 10) || 15));
        var presets = getTimerPresets();
        if (editingPresetIndex >= 0 && editingPresetIndex < presets.length) {
            presets[editingPresetIndex] = val;
            setTimerPresets(presets);
            renderPresets();
        }
        closePresetEdit();
    });

    renderPresets();

    if (btnStart) btnStart.addEventListener('click', function() {
        if (timerInterval) return;
        var min = parseInt(inpMin.value, 10) || 0, sek = parseInt(inpSek.value, 10) || 0;
        timerRest = min * 60 + sek;
        if (timerRest <= 0) return;
        showSet(false);
        timerInterval = setInterval(function() {
            timerRest--;
            if (anzeige) anzeige.textContent = (Math.floor(timerRest / 60) < 10 ? '0' : '') + Math.floor(timerRest / 60) + ':' + (timerRest % 60 < 10 ? '0' : '') + (timerRest % 60);
            if (timerRest <= 0) { clearInterval(timerInterval); timerInterval = null; showSet(true); alert('Timer abgelaufen!'); }
        }, 1000);
    });
    if (btnStop) btnStop.addEventListener('click', function() {
        if (timerInterval) { clearInterval(timerInterval); timerInterval = null; showSet(true); }
    });
    showSet(true);
}

var currentWetterOrt = '';
function loadWetterFromStorage() {
    var subtitle = document.getElementById('wetterSubtitle');
    var leftEl = document.getElementById('wetterAktuell');
    var centerEl = document.getElementById('wetterForecast');
    var rightEl = document.getElementById('wetterDetails');
    if (leftEl) leftEl.innerHTML = '<p class="sleep-wetter-tag">Lade…</p>';
    if (centerEl) centerEl.innerHTML = '';
    if (rightEl) rightEl.innerHTML = '';
    fetch(API_EINSTELLUNGEN).then(function(r) { return r.json(); }).then(function(data) {
        currentWetterOrt = (data.einstellungen && data.einstellungen.wetter_ort) ? String(data.einstellungen.wetter_ort).trim() : '';
        if (subtitle) subtitle.textContent = currentWetterOrt ? currentWetterOrt : 'Ort in Einstellungen festlegen';
        fillWetterPanels(leftEl, centerEl, rightEl, currentWetterOrt);
    }).catch(function() {
        if (leftEl) leftEl.innerHTML = '<p class="sleep-wetter-tag">Einstellungen konnten nicht geladen werden.</p>';
    });
}
function fetchWetterAnzeigen(ort) {
    fillWetterPanels(
        document.getElementById('wetterAktuell'),
        document.getElementById('wetterForecast'),
        document.getElementById('wetterDetails'),
        ort || currentWetterOrt
    );
}
function initWetter() {
    var btn = document.getElementById('wetterAktualisieren');
    if (btn) btn.addEventListener('click', function() {
        if (currentWetterOrt) fetchWetterAnzeigen(currentWetterOrt);
        else loadWetterFromStorage();
    });
}

function loadUpdateTab() {
    var currentEl = document.getElementById('updateCurrentVersion');
    var selectEl = document.getElementById('updateVersionSelect');
    var hintEl = document.getElementById('updateVersionHint');
    var installBtn = document.getElementById('updateInstallBtn');
    if (!selectEl || !installBtn) return;
    selectEl.innerHTML = '<option value="">Lade…</option>';
    installBtn.disabled = true;
    if (currentEl) currentEl.textContent = '…';
    Promise.all([
        fetch(API_UPDATE + '?action=current').then(function(r) { return r.json(); }),
        fetch(API_UPDATE + '?action=list').then(function(r) { return r.json(); })
    ]).then(function(results) {
        var current = results[0];
        var list = results[1];
        if (current && current.ok && current.version != null) {
            if (currentEl) currentEl.textContent = current.version;
        } else if (currentEl) currentEl.textContent = '–';
        var versionen = (list && list.ok && list.versionen) ? list.versionen : [];
        selectEl.innerHTML = '';
        if (versionen.length === 0) {
            selectEl.innerHTML = '<option value="">Keine Versionen geladen</option>';
        } else {
            versionen.forEach(function(v) {
                var opt = document.createElement('option');
                opt.value = v.version || '';
                opt.textContent = (v.version || '–') + ((v.time || v.date) ? ' (' + (v.time || v.date) + ')' : '');
                opt.setAttribute('data-commit', v.commit || '');
                selectEl.appendChild(opt);
            });
            installBtn.disabled = false;
        }
        function updateHint() {
            var opt = selectEl.options[selectEl.selectedIndex];
            var commit = opt && opt.getAttribute('data-commit');
            if (hintEl) hintEl.textContent = commit ? 'Commit: ' + commit : '';
        }
        selectEl.addEventListener('change', updateHint);
        updateHint();
    }).catch(function() {
        if (currentEl) currentEl.textContent = '–';
        selectEl.innerHTML = '<option value="">Fehler beim Laden</option>';
    });
}

function initUpdateTab() {
    var installBtn = document.getElementById('updateInstallBtn');
    var logWrap = document.getElementById('updateLogWrap');
    var logEl = document.getElementById('updateLog');
    var reloadLink = document.getElementById('updateReloadLink');
    if (!installBtn) return;
    installBtn.addEventListener('click', function() {
        var selectEl = document.getElementById('updateVersionSelect');
        var version = selectEl ? selectEl.value : '';
        if (!version) return;
        installBtn.disabled = true;
        if (logWrap) { logWrap.style.display = 'block'; logWrap.scrollIntoView({ behavior: 'smooth' }); }
        if (logEl) logEl.textContent = 'Update wird installiert…\n';
        if (reloadLink) reloadLink.style.display = 'none';
        fetch(API_UPDATE + '?action=install&version=' + encodeURIComponent(version))
            .then(function(r) { return r.json(); })
            .then(function(data) {
                var lines = (data.log && Array.isArray(data.log)) ? data.log : [data.message || 'Fertig'];
                if (logEl) logEl.textContent = lines.join('\n');
                if (data.ok) {
                    if (reloadLink) { reloadLink.style.display = 'inline'; reloadLink.href = '#'; reloadLink.onclick = function(e) { e.preventDefault(); location.reload(); }; }
                    setTimeout(function() { location.reload(); }, 2200);
                }
                installBtn.disabled = false;
            })
            .catch(function(err) {
                if (logEl) logEl.textContent += 'Fehler: ' + (err.message || err) + '\n';
                installBtn.disabled = false;
            });
    });
    if (reloadLink) reloadLink.addEventListener('click', function(e) { e.preventDefault(); location.reload(); });
}

function renderSleepModeContent() {
    var weckerEl = document.getElementById('sleepWeckerListe');
    if (weckerEl) {
        var next = getNextAlarms(5);
        if (next.length === 0) {
            weckerEl.innerHTML = '<div class="sleep-wecker-item">Keine aktiven Wecker</div>';
        } else {
            weckerEl.innerHTML = next.map(function(a) {
                var opts = { weekday: 'short', day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' };
                var meta = a.dateTime.toLocaleDateString('de-DE', opts);
                var zeit = (a.dateTime.getHours() < 10 ? '0' : '') + a.dateTime.getHours() + ':' + (a.dateTime.getMinutes() < 10 ? '0' : '') + a.dateTime.getMinutes();
                return '<div class="sleep-wecker-item"><span class="sleep-wecker-zeit">' + escapeHtml(zeit) + '</span><div class="sleep-wecker-meta">' + escapeHtml(meta) + (a.label ? ' · ' + escapeHtml(a.label) : '') + '</div></div>';
            }).join('');
        }
    }
    var wetterAktuell = document.getElementById('sleepWetterAktuell');
    var wetterForecast = document.getElementById('sleepWetterForecast');
    var wetterDetails = document.getElementById('sleepWetterDetails');
    if (wetterAktuell || wetterForecast || wetterDetails) {
        var showNoOrt = function() {
            var msg = '<p class="sleep-wetter-tag">Ort in Einstellungen festlegen</p>';
            if (wetterAktuell) wetterAktuell.innerHTML = msg;
            if (wetterForecast) wetterForecast.innerHTML = '';
            if (wetterDetails) wetterDetails.innerHTML = '';
        };
        if (!currentWetterOrt) {
            fetch(API_EINSTELLUNGEN).then(function(r) { return r.json(); }).then(function(data) {
                currentWetterOrt = (data.einstellungen && data.einstellungen.wetter_ort) ? String(data.einstellungen.wetter_ort).trim() : '';
                if (currentWetterOrt) fetchWetterForSleepMode(currentWetterOrt);
                else showNoOrt();
            }).catch(function() { showNoOrt(); });
        } else {
            fetchWetterForSleepMode(currentWetterOrt);
        }
    }
}

function sleepWetterIconName(code) {
    var c = parseInt(code, 10);
    if (isNaN(c) || c < 0) return 'cloudy';
    if (c === 0) return 'clear';
    if (c === 1 || c === 2) return 'partly-cloudy';
    if (c === 3) return 'cloudy';
    if (c === 45 || c === 48) return 'fog';
    if (c >= 51 && c <= 55) return 'rain';
    if (c >= 61 && c <= 65 || c >= 80 && c <= 82) return 'rain';
    if (c >= 71 && c <= 77 || c >= 85 && c <= 86) return 'snow';
    if (c >= 95 && c <= 99) return 'thunderstorm';
    return 'cloudy';
}
function sleepWetterIconUrl(code) {
    if (code == null || code === '') return '';
    var name = sleepWetterIconName(code);
    return '/icons/weather/' + name + '.svg';
}

function fillWetterPanelsFromData(leftEl, centerEl, rightEl, data) {
    var cur = data.current || {};
    var daily = data.daily || [];
    var code = cur.code != null ? cur.code : 0;
    var iconUrl = sleepWetterIconUrl(code);
    var day0 = daily[0];
    var tempMax = day0 && day0.temp_max != null ? day0.temp_max : null;
    var tempMin = day0 && day0.temp_min != null ? day0.temp_min : null;
    if (leftEl) {
        var left = '';
        left += '<div class="sleep-now-icon-wrap">';
        if (iconUrl) left += '<img src="' + escapeHtml(iconUrl) + '" alt="" class="sleep-now-icon" loading="lazy">';
        left += '</div>';
        left += '<div class="sleep-now-desc">' + escapeHtml(cur.beschreibung || '') + '</div>';
        left += '<div class="sleep-now-temp">' + escapeHtml(String(cur.temp != null ? cur.temp : '–')) + '°</div>';
        left += '<div class="sleep-highlow">';
        if (tempMax != null) left += '<span>↑ ' + escapeHtml(String(tempMax)) + '°</span>';
        if (tempMin != null) left += '<span>↓ ' + escapeHtml(String(tempMin)) + '°</span>';
        left += '</div>';
        left += '<div class="sleep-strip">';
        left += '<span>Wolken ' + (cur.cloud_cover != null ? cur.cloud_cover + '%' : '–') + '</span>';
        left += '<span>Luftfeuchtigkeit ' + (cur.humidity != null ? cur.humidity + '%' : '–') + '</span>';
        left += '<span>Gefühlt ' + (cur.apparent_temperature != null ? cur.apparent_temperature + '°' : '–') + '</span>';
        left += '</div>';
        leftEl.innerHTML = left;
    }
    if (centerEl) {
        var center = '';
        center += '<div class="sleep-forecast-list">';
        daily.slice(0, 8).forEach(function(d) {
            var rowIcon = sleepWetterIconUrl(d.code != null ? d.code : 0);
            var tStr = (d.temp_max != null ? d.temp_max + '°' : '') + (d.temp_min != null ? ' / ' + d.temp_min + '°' : '');
            center += '<div class="sleep-forecast-row">';
            if (rowIcon) center += '<img src="' + escapeHtml(rowIcon) + '" alt="" class="sleep-forecast-icon" loading="lazy">';
            center += '<span class="sleep-forecast-date">' + escapeHtml(d.datum_short || d.datum || '') + '</span>';
            center += '<span class="sleep-forecast-temp">' + escapeHtml(tStr) + '</span>';
            center += '</div>';
        });
        center += '</div>';
        if (day0 && (day0.sunrise || day0.sunset)) {
            center += '<div class="sleep-sun">';
            if (day0.sunrise) center += '<span>☀ ' + escapeHtml(day0.sunrise) + '</span>';
            if (day0.sunset) center += '<span>🌙 ' + escapeHtml(day0.sunset) + '</span>';
            center += '</div>';
        }
        centerEl.innerHTML = center;
    }
    if (rightEl) {
        var uv = (day0 && day0.uv_index_max != null) ? day0.uv_index_max : (cur.uv_index != null ? cur.uv_index : null);
        var rainChance = day0 && day0.precipitation_probability_max != null ? day0.precipitation_probability_max : null;
        var dayLight = day0 && day0.daylight_duration != null ? day0.daylight_duration : null;
        var right = '';
        right += '<div class="sleep-details">';
        right += '<div class="sleep-detail-row"><span class="label">Wind</span><span class="value">' + (cur.wind_speed != null ? cur.wind_speed + ' km/h' : '–') + '</span></div>';
        right += '<div class="sleep-detail-row"><span class="label">Richtung</span><span class="value">' + (cur.wind_direction != null ? cur.wind_direction + '°' : '–') + '</span></div>';
        right += '<div class="sleep-detail-row"><span class="label">UV-Index</span><span class="value">' + (uv != null ? uv : '–') + '</span></div>';
        right += '<div class="sleep-detail-row"><span class="label">Niederschlag</span><span class="value">' + (rainChance != null ? rainChance + '%' : '–') + '</span></div>';
        right += '<div class="sleep-detail-row"><span class="label">Sichtweite</span><span class="value">' + (cur.visibility != null ? cur.visibility + ' km' : '–') + '</span></div>';
        right += '<div class="sleep-detail-row"><span class="label">Luftdruck</span><span class="value">' + (cur.pressure != null ? cur.pressure + ' hPa' : '–') + '</span></div>';
        right += '<div class="sleep-detail-row"><span class="label">Tageslicht</span><span class="value">' + (dayLight != null ? dayLight + ' h' : '–') + '</span></div>';
        right += '</div>';
        rightEl.innerHTML = right;
    }
}

function fillWetterPanels(leftEl, centerEl, rightEl, ort) {
    if (!leftEl && !centerEl && !rightEl) return;
    var loading = '<p class="sleep-wetter-tag">Lade…</p>';
    var noOrt = '<p class="sleep-wetter-tag">Ort in Einstellungen festlegen</p>';
    if (!ort || ort === '') {
        if (leftEl) leftEl.innerHTML = noOrt;
        if (centerEl) centerEl.innerHTML = '';
        if (rightEl) rightEl.innerHTML = '';
        return;
    }
    if (leftEl) leftEl.innerHTML = loading;
    if (centerEl) centerEl.innerHTML = '';
    if (rightEl) rightEl.innerHTML = '';
    fetch('api_wetter.php?plz=' + encodeURIComponent(ort)).then(function(r) { return r.json(); }).then(function(data) {
        if (!data.ok) {
            var err = '<p class="sleep-wetter-tag">' + escapeHtml(data.error || 'Fehler') + '</p>';
            if (leftEl) leftEl.innerHTML = err;
            return;
        }
        fillWetterPanelsFromData(leftEl, centerEl, rightEl, data);
    }).catch(function() {
        var err = '<p class="sleep-wetter-tag">Netzwerkfehler</p>';
        if (leftEl) leftEl.innerHTML = err;
    });
}

function fetchWetterForSleepMode(ort) {
    fillWetterPanels(
        document.getElementById('sleepWetterAktuell'),
        document.getElementById('sleepWetterForecast'),
        document.getElementById('sleepWetterDetails'),
        ort
    );
}

var sleepModeInactivityAt = 0;
var sleepModeVisible = false;

function showSleepMode() {
    if (sleepModeVisible) return;
    sleepModeVisible = true;
    var overlay = document.getElementById('sleepModeOverlay');
    if (overlay) {
        overlay.classList.add('visible');
        overlay.setAttribute('aria-hidden', 'false');
        renderSleepModeContent();
    }
}

function hideSleepMode() {
    if (!sleepModeVisible) return;
    sleepModeVisible = false;
    var overlay = document.getElementById('sleepModeOverlay');
    if (overlay) {
        overlay.classList.remove('visible');
        overlay.setAttribute('aria-hidden', 'true');
    }
    resetSleepModeTimer();
}

function resetSleepModeTimer() {
    sleepModeInactivityAt = Date.now();
}

function checkSleepModeTimer() {
    if (!isSleepModeHostErlaubt()) return;
    if (sleepModeSekunden <= 0) return;
    if (sleepModeVisible) return;
    var elapsed = (Date.now() - sleepModeInactivityAt) / 1000;
    if (elapsed >= sleepModeSekunden) showSleepMode();
}

function initSleepMode() {
    resetSleepModeTimer();
    if (isSleepModeHostErlaubt()) setInterval(checkSleepModeTimer, 1000);
    var overlay = document.getElementById('sleepModeOverlay');
    if (overlay) overlay.addEventListener('click', function() { hideSleepMode(); });
    ['mousemove', 'mousedown', 'keydown', 'touchstart', 'scroll'].forEach(function(ev) {
        document.addEventListener(ev, function() {
            resetSleepModeTimer();
            if (sleepModeVisible) hideSleepMode();
        }, { passive: true });
    });
}

if ('serviceWorker' in navigator) {
    window.addEventListener('load', function () {
        navigator.serviceWorker.register('/sw.js').catch(function () {});
    });
}

window.neuWecker = neuWecker;
window.openSettings = openSettings;
window.toggleTheme = toggleTheme;
window.editWecker = editWecker;
window.closeEdit = closeEdit;
window.toggleWecker = toggleWecker;
window.deleteWecker = deleteWecker;
window.saveSettings = saveSettings;
window.closeSettings = closeSettings;
</script>

</body>
</html>
