<?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();
$wochentage_map = function_wecker_hole_wochentage_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, $wochentage_map);
$zeitzonen = timezone_identifiers_list();
$sleep_mode_sekunden = (int)(function_einstellung_hole('sleep_mode_sekunden') ?? 0);
$accent_color_server = trim((string)(function_einstellung_hole('accent_color') ?? ''));
if ($accent_color_server !== '' && !preg_match('/^#[0-9a-fA-F]{3}$|^#[0-9a-fA-F]{6}$/', $accent_color_server)) {
    $accent_color_server = '';
}
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>var __INITIAL_ACCENT = <?php echo json_encode($accent_color_server); ?>;</script>
<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';
    var accent = localStorage.getItem('wecker-accent');
    if (!accent && typeof __INITIAL_ACCENT === 'string' && __INITIAL_ACCENT) accent = __INITIAL_ACCENT;
    if (accent) document.documentElement.style.setProperty('--accent', accent);
})();
</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;
    display: flex;
    align-items: center;
    gap: 8px;
}
.live-dot {
    display: inline-block;
    width: 8px; height: 8px;
    border-radius: 50%;
    background: var(--accent, #2563eb);
    opacity: 0.85;
    animation: live-pulse 2s ease-in-out infinite;
    flex-shrink: 0;
}
@keyframes live-pulse {
    0%, 100% { opacity: 0.85; transform: scale(1); }
    50% { opacity: 0.3; transform: scale(0.7); }
}

.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 */
.header-update-bar {
    display: none;
    justify-content: flex-end;
    align-items: center;
    gap: 6px;
    margin-top: 8px;
}
.header-update-bar.visible { display: flex; }
.header-update-btn {
    min-height: 36px;
    padding: 0 14px;
    background: var(--accent);
    color: #fff;
    border: none;
    border-radius: 8px;
    font-size: 0.9rem;
    font-weight: 600;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    touch-action: manipulation;
}
.header-update-btn:hover { opacity: 0.88; }
.header-update-btn:active { transform: scale(0.96); }
.header-update-close {
    min-width: 36px;
    min-height: 36px;
    padding: 0;
    background: var(--card-hover);
    border: none;
    border-radius: 8px;
    color: var(--text-muted);
    font-size: 1.2rem;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    touch-action: manipulation;
}
.header-update-close:hover { background: var(--card-border); color: var(--text); }

.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);
}
@media (min-width: 600px) {
    .overlay-box {
        max-width: 680px;
        max-height: 92dvh;
        padding: 24px 28px;
    }
    #settingsOverlay .overlay-box {
        min-height: 72dvh;
    }
    #settingsOverlay .settings-tab-panels {
        min-height: 52dvh;
    }
}
@media (min-width: 960px) {
    .overlay-box { max-width: 820px; }
}

.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 input[type="number"],
.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 input[type="number"] { max-width: 10rem; }
.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; }

.sprachtext-toggle-row { display: flex; gap: 8px; margin-bottom: 8px; }
.sprachtext-toggle-btn {
    padding: 8px 14px;
    font-size: 0.875rem;
    border: 1px solid var(--card-border);
    background: var(--input-bg);
    color: var(--text-muted);
    border-radius: 8px;
    cursor: pointer;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.sprachtext-toggle-btn:hover { background: var(--card-hover); color: var(--text); }
.sprachtext-toggle-btn.active {
    background: var(--accent);
    color: #fff;
    border-color: var(--accent);
}
.sprachtext-preview-wrap {
    min-height: 88px;
    padding: 12px 14px;
    background: var(--input-bg);
    border: 1px solid var(--card-border);
    border-radius: 12px;
    color: var(--text);
    font-size: 1rem;
    line-height: 1.5;
    white-space: pre-wrap;
    word-break: break-word;
}

.wochentage-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.wochentage-row input[type="checkbox"] { display: none; }
.wochentage-row label {
    display: flex; align-items: center; justify-content: center;
    min-width: 44px; min-height: 44px;
    padding: 6px 10px;
    margin: 0;
    cursor: pointer;
    border-radius: 10px;
    border: 2px solid var(--card-border);
    background: var(--input-bg);
    color: var(--text-muted);
    font-weight: 500;
    font-size: 0.95rem;
    user-select: none;
    touch-action: manipulation;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.wochentage-row input[type="checkbox"]:checked + span {
    /* über JS gesteuert */
}
.wochentage-row label.tag-aktiv {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
}
[data-theme="dark"] .wochentage-row label.tag-aktiv { color: #fff; }

.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-search-wrap { position: relative; }
.audio-search-input { width: 100%; padding: 8px 10px; font-size: 0.875rem; background: var(--input-bg); border: 1px solid var(--card-border); border-radius: 8px; box-sizing: border-box; }
.audio-search-results { position: absolute; left: 0; right: 0; top: 100%; margin: 2px 0 0 0; padding: 4px 0; list-style: none; background: var(--card-bg); border: 1px solid var(--card-border); border-radius: 8px; max-height: 200px; overflow-y: auto; z-index: 100; box-shadow: 0 4px 12px rgba(0,0,0,0.15); }
.audio-search-results[aria-hidden="true"] { display: none; }
.audio-search-results li { padding: 8px 12px; cursor: pointer; font-size: 0.875rem; display: flex; align-items: center; gap: 8px; }
.audio-search-results li:hover { background: var(--card-hover); }
.audio-search-results li .audio-search-type { font-size: 0.75rem; color: var(--text-muted); }

#settingsOverlay .overlay-box {
    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;
}
#editBox {
    display: flex;
    flex-direction: column;
}
#editBoxContent {
    flex: 1;
    min-height: 0;
    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;
    overflow-x: auto;
    scrollbar-width: none;
}
.settings-tabs::-webkit-scrollbar { display: none; }
.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;
    white-space: nowrap;
    flex-shrink: 0;
}
@media (min-width: 600px) {
    .settings-tabs { margin: -24px -28px 20px -28px; padding: 0 16px; }
    .settings-tabs button { padding: 14px 18px; font-size: 0.95rem; }
    #editBox { max-height: 92dvh; }
}
.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); }
.settings-section.sleep-mode-section { padding: 14px 0; border-top: 1px solid var(--card-border); }
.settings-section.sleep-mode-section:first-child { border-top: none; padding-top: 0; }
.settings-section.sleep-mode-section .form-row { margin-bottom: 0; }
.settings-section.sleep-mode-section .platzhalter-hint { margin-top: 10px; padding: 10px 12px; background: var(--input-bg); border-radius: 8px; border-left: 3px solid var(--accent); }
.info-value { margin: 0; font-family: ui-monospace, monospace; font-size: 1rem; color: var(--text); }
.info-cron-status { margin: 6px 0 0 0; padding: 8px 12px; border-radius: 8px; font-size: 0.9rem; font-weight: 500; }
.info-cron-status.status-ok   { background: rgba(34, 197, 94, 0.2);  color: #16a34a; }
.info-cron-status.status-warn { background: rgba(234, 179, 8, 0.2);   color: #ca8a04; }
.info-cron-status.status-error { background: rgba(239, 68, 68, 0.15); color: var(--danger); }
[data-theme="dark"] .info-cron-status.status-ok   { background: rgba(34, 197, 94, 0.25);  color: #4ade80; }
[data-theme="dark"] .info-cron-status.status-warn { background: rgba(234, 179, 8, 0.25);   color: #facc15; }
[data-theme="dark"] .info-cron-status.status-error { background: rgba(239, 68, 68, 0.2);   color: #f87171; }
.cron-job-list { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }
.cron-job-item {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    padding: 10px 12px; border-radius: 10px;
    background: var(--input-bg); border: 1px solid var(--card-border);
}
.cron-job-info { flex: 1; min-width: 0; }
.cron-job-label { font-weight: 500; font-size: 0.95rem; color: var(--text); }
.cron-job-meta { font-size: 0.78rem; color: var(--text-muted); margin-top: 2px; font-family: ui-monospace, monospace; }
.cron-job-run-btn {
    flex-shrink: 0;
    min-height: 36px; padding: 0 14px;
    background: var(--accent); color: #fff;
    border: none; border-radius: 8px;
    font-size: 0.88rem; font-weight: 600; cursor: pointer;
    touch-action: manipulation; white-space: nowrap;
}
.cron-job-run-btn:hover { opacity: 0.88; }
.cron-job-run-btn:active { transform: scale(0.96); }
.cron-job-run-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.cleanup-list { display: flex; flex-direction: column; margin-top: 10px; border: 1px solid var(--card-border); border-radius: 10px; overflow: hidden; }
.cleanup-item { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 12px; border-bottom: 1px solid var(--card-border); background: var(--input-bg); }
.cleanup-item:last-child { border-bottom: none; }
.cleanup-item-info { flex: 1; min-width: 0; }
.cleanup-item-label { font-weight: 500; font-size: 0.92rem; color: var(--text); }
.cleanup-item-meta { font-size: 0.78rem; color: var(--text-muted); margin-top: 2px; }
.cleanup-item-actions { display: flex; gap: 6px; flex-shrink: 0; }
.cleanup-btn { flex-shrink: 0; min-height: 32px; padding: 0 12px; background: var(--accent); color: #fff; border: none; border-radius: 8px; font-size: 0.85rem; font-weight: 600; cursor: pointer; touch-action: manipulation; white-space: nowrap; }
.cleanup-btn:hover { opacity: 0.88; }
.cleanup-btn:active { transform: scale(0.96); }
.cleanup-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.cleanup-btn.danger { background: var(--danger, #e74c3c); }
.cleanup-log { margin-top: 10px; background: var(--input-bg); border: 1px solid var(--card-border); border-radius: 8px; padding: 10px 12px; font-size: 0.82rem; font-family: ui-monospace, monospace; color: var(--text-muted); white-space: pre-wrap; }
.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-mode-time {
    flex-shrink: 0;
    text-align: center;
    padding: 12px 0 16px;
    color: var(--text);
}
#sleepModeOverlay .sleep-mode-time-clock {
    display: block;
    font-size: clamp(2.5rem, 10vw, 4rem);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.02em;
    line-height: 1.1;
    color: var(--text);
}
#sleepModeOverlay .sleep-mode-time-date {
    display: block;
    font-size: 1rem;
    color: var(--text-muted);
    margin-top: 4px;
}
#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;
}
/* Wetter-Icons sind weiße SVGs – im Light-Mode invertieren damit sie sichtbar sind */
.sleep-now-icon, .sleep-forecast-icon { filter: invert(1) opacity(0.8); }
[data-theme="dark"] .sleep-now-icon, [data-theme="dark"] .sleep-forecast-icon { filter: none; }
#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;
    height: calc(100dvh - 56px - var(--safe-bottom));
    overflow: hidden;
    padding-bottom: 0;
}
#panelWetter .panel-header { flex-shrink: 0; }
#panelWetter .panel-inner.wetter-dashboard {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    padding: 12px 14px;
    max-width: none;
    overflow: hidden;
}
#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: 14px;
    padding: 12px;
    overflow: hidden;
}
#panelWetter .panel-inner .sleep-title { font-size: 0.75rem; color: var(--text-muted); margin-bottom: 6px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; }
#panelWetter .panel-inner .sleep-now-icon-wrap { display: flex; justify-content: center; margin: 6px 0; }
#panelWetter .panel-inner .sleep-now-icon { width: 64px; height: 64px; object-fit: contain; }
#panelWetter .panel-inner .sleep-now-desc { font-size: 0.85rem; color: var(--text-muted); text-align: center; margin-bottom: 4px; }
#panelWetter .panel-inner .sleep-now-temp { font-size: 2rem; 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: 10px; justify-content: center; margin: 6px 0; flex-wrap: wrap; }
#panelWetter .panel-inner .sleep-highlow span { display: inline-flex; align-items: center; gap: 4px; font-size: 0.88rem; color: var(--text-muted); }
#panelWetter .panel-inner .sleep-strip { display: flex; flex-wrap: wrap; gap: 8px 12px; margin-top: auto; padding-top: 8px; border-top: 1px solid var(--card-border); font-size: 0.8rem; 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: 8px; padding: 5px 0; border-bottom: 1px solid var(--card-border); font-size: 0.85rem; color: var(--text); }
#panelWetter .panel-inner .sleep-forecast-row:last-child { border-bottom: none; }
#panelWetter .panel-inner .sleep-forecast-icon { width: 24px; height: 24px; object-fit: contain; flex-shrink: 0; }
#panelWetter .panel-inner .sleep-forecast-date { min-width: 32px; 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: 12px; margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--card-border); font-size: 0.85rem; color: var(--text-muted); }
#panelWetter .panel-inner .sleep-sun span { display: inline-flex; align-items: center; gap: 4px; }
#panelWetter .panel-inner .sleep-details { display: flex; flex-direction: column; gap: 5px; 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.85rem; 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: 8px; border-top: 1px solid var(--card-border); font-size: 0.78rem; color: var(--text-muted); }
#panelWetter .panel-inner .sleep-wetter-tag { font-size: 0.85rem; padding: 4px 0; color: var(--text-muted); }

/* WLAN-Tab */
.wlan-status-box {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 12px 14px;
    background: var(--card);
    border: 1px solid var(--card-border);
    border-radius: 12px;
    font-size: 0.9rem;
    color: var(--text);
}
.wlan-status-dot {
    width: 10px; height: 10px;
    border-radius: 50%;
    flex-shrink: 0;
    background: var(--text-muted);
}
.wlan-status-dot.connected { background: #22c55e; }
.wlan-status-dot.disconnected { background: #ef4444; }
.wlan-status-info { display: flex; flex-direction: column; gap: 2px; }
.wlan-status-ssid { font-weight: 600; color: var(--text); }
.wlan-status-ip { font-size: 0.8rem; color: var(--text-muted); }
.wlan-network-list { display: flex; flex-direction: column; gap: 6px; }
.wlan-network-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    background: var(--card);
    border: 1px solid var(--card-border);
    border-radius: 10px;
    cursor: pointer;
    transition: background 0.15s;
}
.wlan-network-item:hover { background: var(--card-hover); }
.wlan-network-item.in-use { border-color: var(--accent); }
.wlan-network-ssid { flex: 1; font-size: 0.9rem; color: var(--text); font-weight: 500; }
.wlan-network-signal { font-size: 0.75rem; color: var(--text-muted); font-family: monospace; }
.wlan-network-badge {
    font-size: 0.7rem;
    padding: 2px 6px;
    border-radius: 6px;
    background: var(--card-hover);
    color: var(--text-muted);
}
.wlan-network-badge.open { background: #22c55e22; color: #22c55e; }
.wlan-network-badge.active { background: var(--accent); color: #fff; }
.wlan-msg {
    padding: 8px 12px;
    border-radius: 8px;
    font-size: 0.85rem;
    margin-top: 6px;
}
.wlan-msg.ok { background: #22c55e22; color: #22c55e; }
.wlan-msg.error { background: #ef444422; color: #ef4444; }
</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 <span id="weckerLiveIndicator" class="live-dot" title="Live-Sync aktiv"></span></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" id="themeToggle" aria-label="Dark/Light Mode" title="Dark/Light Mode"></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>
        </div>
    </div>
    <div class="header-update-bar" id="updateBar" aria-live="polite">
        <button type="button" class="header-update-btn" id="updateToastToSettings">&#8593; Update verfügbar</button>
        <button type="button" class="header-update-close" id="updateToastClose" aria-label="Schließen">&times;</button>
    </div>
</header>
<main class="main" id="weckerListe">
<?php
foreach ($wecker as $w):
    $aktiv = (bool)($w['aktiv'] ?? 0);
    $wochentage = ($w['wiederholung'] ?? '') === 'woechentlich' ? ($wochentage_map[(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" id="themeToggle4" aria-label="Dark/Light Mode"></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>
            </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" id="themeToggle5" aria-label="Dark/Light Mode"></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>
            </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" id="themeToggleMusik" aria-label="Dark/Light Mode"></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>
            </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: Uhr + 3 Spalten (Aktuell | 8-Tage | Details) + Wecker-Leiste -->
<div id="sleepModeOverlay" class="sleep-mode-overlay" aria-hidden="true">
    <div id="sleepModeTime" class="sleep-mode-time" aria-live="polite">
        <span id="sleepModeTimeClock" class="sleep-mode-time-clock">00:00</span>
        <span id="sleepModeTimeDate" class="sleep-mode-time-date"></span>
    </div>
    <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>


<!-- 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" id="wochentageRow">
                    <?php foreach ($wochentage_labels as $num => $label): ?>
                    <label id="tagLabel<?= $num ?>">
                        <input type="checkbox" name="wochentage[]" value="<?= $num ?>" id="tagCb<?= $num ?>">
                        <span><?= $label ?></span>
                    </label>
                    <?php endforeach; ?>
                </div>
            </div>
            <div class="form-row">
                <label>Sprachtext (TTS)</label>
                <div class="sprachtext-toggle-row">
                    <button type="button" class="sprachtext-toggle-btn active" id="editSprachtextTogglePlaceholder" aria-pressed="true">Platzhalter</button>
                    <button type="button" class="sprachtext-toggle-btn" id="editSprachtextTogglePreview" aria-pressed="false">Vorschau</button>
                </div>
                <div id="editSprachtextWrap" class="sprachtext-edit-wrap">
                    <textarea name="sprachtext" id="editSprachtext" required></textarea>
                </div>
                <div id="editSprachtextPreview" class="sprachtext-preview-wrap" style="display:none;" aria-hidden="true"></div>
                <p class="platzhalter-hint">Platzhalter per Drag &amp; Drop in den Text ziehen oder antippen (Touch) zum Einfügen an der Cursorposition:</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 oder antippen zum Einfügen"><?= htmlspecialchars($ph) ?></span><?php endforeach; ?>
                </div>
            </div>
            <div class="form-row audio-section">
                <label>Musik beim Wecken</label>
                <p class="platzhalter-hint">Optional: Playlist oder einzelnes Lied aus deiner Musik – gleichzeitig mit TTS oder danach.</p>
                <div class="audio-groups">
                    <div class="audio-group">
                        <span class="audio-group-title">Gleichzeitig mit TTS</span>
                        <ul id="audioListGleichzeitig" class="audio-list"></ul>
                        <div class="audio-search-wrap">
                            <input type="text" id="audioSearchGleichzeitig" class="audio-search-input" placeholder="Playlist oder Lied suchen…" autocomplete="off" data-position="gleichzeitig">
                            <ul id="audioSearchResultsGleichzeitig" class="audio-search-results" aria-hidden="true"></ul>
                        </div>
                    </div>
                    <div class="audio-group">
                        <span class="audio-group-title">Danach</span>
                        <ul id="audioListDanach" class="audio-list"></ul>
                        <div class="audio-search-wrap">
                            <input type="text" id="audioSearchDanach" class="audio-search-input" placeholder="Playlist oder Lied suchen…" autocomplete="off" data-position="danach">
                            <ul id="audioSearchResultsDanach" class="audio-search-results" aria-hidden="true"></ul>
                        </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="settingsTabBtnPlatzhalter" aria-selected="false" aria-controls="settingsTabPlatzhalter" data-tab="settingsTabPlatzhalter">Platzhalter</button>
            <button type="button" role="tab" id="settingsTabBtnInfo" aria-selected="false" aria-controls="settingsTabInfo" data-tab="settingsTabInfo">Info/Debug</button>
            <button type="button" role="tab" id="settingsTabBtnUpdate" aria-selected="false" aria-controls="settingsTabUpdate" data-tab="settingsTabUpdate">Update</button>
            <button type="button" role="tab" id="settingsTabBtnWlan" aria-selected="false" aria-controls="settingsTabWlan" data-tab="settingsTabWlan">WLAN</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>Name (für TTS)</h3>
                <div class="form-row">
                    <label>Ihr Name</label>
                    <input type="text" id="settingTtsName" placeholder="z.B. Max" maxlength="100" autocomplete="name">
                    <p class="platzhalter-hint">Wird im Wecker-Sprachtext für <code>{{name}}</code> verwendet.</p>
                </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>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 class="settings-section sleep-mode-section">
                <h3>Sleep-Mode</h3>
                <div class="form-row">
                    <label for="settingSleepModeSekunden">Sleep-Mode starten nach … Sekunden (0 = aus)</label>
                    <input type="number" id="settingSleepModeSekunden" min="0" max="86400" value="0" placeholder="z.B. 30" aria-describedby="sleepModeHint">
                    <p id="sleepModeHint" 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>Welcher TTS-Anbieter wird verwendet?</label>
                    <select id="settingTtsProvider">
                        <option value="piper">Piper (lokal)</option>
                        <option value="openai">OpenAI (API)</option>
                    </select>
                    <p class="platzhalter-hint">Mit dem Dropdown legen Sie fest, ob TTS über Piper (lokales Skript) oder über die OpenAI-API erzeugt wird.</p>
                </div>
                <div id="ttsPiperOptions" class="tts-provider-options">
                    <div class="form-row">
                        <label>Sprachmodell (Piper)</label>
                        <select id="settingTtsModell">
                            <?php foreach ($tts_models as $m): ?>
                            <option value="<?= htmlspecialchars($m['id']) ?>"><?= htmlspecialchars($m['label']) ?></option>
                            <?php endforeach; ?>
                        </select>
                    </div>
                </div>
                <div id="ttsOpenaiOptions" class="tts-provider-options" style="display:none;">
                    <div class="form-row">
                        <label>OpenAI API-Key</label>
                        <input type="password" id="settingTtsOpenaiApiKey" placeholder="sk-…" autocomplete="off">
                        <p class="platzhalter-hint">Leer lassen, um den gespeicherten Key beizubehalten.</p>
                    </div>
                    <div class="form-row">
                        <label>OpenAI Modell</label>
                        <select id="settingTtsOpenaiModel">
                            <option value="tts-1">tts-1</option>
                            <option value="tts-1-hd">tts-1-hd</option>
                            <option value="gpt-4o-mini-tts">gpt-4o-mini-tts</option>
                            <option value="gpt-4o-mini-tts-2025-12-15">gpt-4o-mini-tts-2025-12-15</option>
                        </select>
                    </div>
                    <div class="form-row">
                        <label>Stimme</label>
                        <select id="settingTtsOpenaiVoice">
                            <option value="alloy">alloy</option>
                            <option value="ash">ash</option>
                            <option value="ballad">ballad</option>
                            <option value="echo">echo</option>
                            <option value="fable">fable</option>
                            <option value="onyx">onyx</option>
                            <option value="nova">nova</option>
                            <option value="shimmer">shimmer</option>
                            <option value="coral">coral</option>
                            <option value="sage">sage</option>
                            <option value="verse">verse</option>
                            <option value="marin">marin</option>
                            <option value="cedar">cedar</option>
                        </select>
                    </div>
                </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="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="settingsTabInfo" class="settings-tab-panel" role="tabpanel" aria-labelledby="settingsTabBtnInfo">
            <div class="settings-section">
                <h3>System-Info</h3>
                <div class="form-row">
                    <label>IP-Adresse (Server)</label>
                    <p id="infoIp" class="info-value">–</p>
                </div>
                <div class="form-row">
                    <label>Letzter Cron-Lauf (Master)</label>
                    <p id="infoCronTime" class="info-value">–</p>
                    <p id="infoCronStatus" class="info-cron-status" role="status" aria-live="polite"></p>
                </div>
                <div class="form-row">
                    <label>Einzelne Jobs starten</label>
                    <div id="cronJobList" class="cron-job-list">
                        <p class="platzhalter-hint" id="cronJobListHint">Lade Jobs…</p>
                    </div>
                    <button type="button" class="header-btn" id="btnCronRunOnce" style="margin-top:10px">Alle Jobs starten</button>
                </div>
                <div class="form-row">
                    <label>Cache</label>
                    <p class="platzhalter-hint">Wetter- und TTS-Cache leeren (z. B. nach Änderungen an Orten oder Sprachtexten).</p>
                    <button type="button" class="header-btn" id="btnResetCache" style="margin-top:8px">Cache zurücksetzen</button>
                </div>
                <div class="form-row">
                    <label>Aufräumen</label>
                    <p class="platzhalter-hint">Alte Backups, Logs und temporäre Dateien analysieren und löschen.</p>
                    <button type="button" class="header-btn" id="btnCleanupScan" style="margin-top:8px">Scannen</button>
                    <div id="cleanupResult" style="display:none; margin-top:12px;"></div>
                </div>
                <div class="form-row">
                    <label>System</label>
                    <p class="platzhalter-hint">Raspberry Pi neu starten oder herunterfahren.</p>
                    <div style="display:flex; gap:8px; margin-top:8px; flex-wrap:wrap;">
                        <button type="button" class="header-btn" id="btnReboot">Neu starten</button>
                        <button type="button" class="header-btn danger" id="btnShutdown">Herunterfahren</button>
                    </div>
                    <p id="systemCmdMsg" class="platzhalter-hint" style="margin-top:8px; display:none;"></p>
                </div>
            </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="settingsTabWlan" class="settings-tab-panel" role="tabpanel" aria-labelledby="settingsTabBtnWlan">
            <div class="settings-section">
                <h3>WLAN-Status</h3>
                <div id="wlanStatus" class="wlan-status-box">
                    <span class="wlan-status-text">Wird geladen…</span>
                </div>
                <div class="form-row" style="margin-top:10px; display:flex; gap:8px; flex-wrap:wrap;">
                    <button type="button" class="header-btn" id="wlanScanBtn" onclick="wlanScan()">Netzwerke scannen</button>
                    <button type="button" class="header-btn" id="wlanDisconnectBtn" onclick="wlanDisconnect()" style="display:none">Trennen</button>
                </div>
            </div>
            <div class="settings-section" id="wlanNetworkSection" style="display:none">
                <h3>Verfügbare Netzwerke</h3>
                <div id="wlanNetworkList" class="wlan-network-list"></div>
            </div>
            <div class="settings-section" id="wlanConnectSection" style="display:none">
                <h3>Verbinden mit: <span id="wlanConnectSsidLabel"></span></h3>
                <div class="form-row">
                    <label>Passwort</label>
                    <input type="password" id="wlanPassword" placeholder="WLAN-Passwort" autocomplete="new-password">
                </div>
                <div id="wlanConnectMsg" class="wlan-msg" style="display:none"></div>
                <div style="display:flex; gap:8px; flex-wrap:wrap; margin-top:8px;">
                    <button type="button" class="header-btn" id="wlanConnectBtn" onclick="wlanConnect()">Verbinden</button>
                    <button type="button" class="header-btn" onclick="wlanCancelConnect()">Abbrechen</button>
                </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_INFO = 'api_info.php';
const API_MUSIK = 'api_musik.php';
const API_ICONS = 'api_icons.php';
const API_WLAN  = 'api_wlan.php';
var weckerListeData = <?= json_encode($wecker_fuer_js) ?>;
var _weckerLiveHash = JSON.stringify(weckerListeData);
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(); });
}

/** Wochentag-Toggle-Buttons: Klick/Touch synchronisiert die versteckte Checkbox und die aktiv-Klasse. */
function syncWochentageAktivKlasse() {
    document.querySelectorAll('#wochentageRow input[type="checkbox"]').forEach(function(cb) {
        var label = cb.closest('label');
        if (label) label.classList.toggle('tag-aktiv', cb.checked);
    });
}

function initWochentageToggle() {
    document.querySelectorAll('#wochentageRow label').forEach(function(label) {
        label.addEventListener('click', function(e) {
            e.preventDefault();
            var cb = label.querySelector('input[type="checkbox"]');
            if (!cb) return;
            cb.checked = !cb.checked;
            label.classList.toggle('tag-aktiv', cb.checked);
        });
    });
}

/** Platzhalter-Text an Cursorposition in der Sprachtext-Textarea einfügen (für Drop und Tap). */
function insertPlatzhalterInSprachtext(ta, text) {
    if (!ta || !text) return;
    ta.focus();
    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;
}

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';
        });
        chip.addEventListener('click', function(e) {
            var ph = chip.getAttribute('data-placeholder') || '';
            if (ph) insertPlatzhalterInSprachtext(ta, ph);
        });
    });
    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) insertPlatzhalterInSprachtext(ta, text);
    });
}

function initSprachtextToggle() {
    var btnPlaceholder = document.getElementById('editSprachtextTogglePlaceholder');
    var btnPreview = document.getElementById('editSprachtextTogglePreview');
    var wrap = document.getElementById('editSprachtextWrap');
    var previewDiv = document.getElementById('editSprachtextPreview');
    var ta = document.getElementById('editSprachtext');
    if (!btnPlaceholder || !btnPreview || !wrap || !previewDiv || !ta) return;

    function showPlaceholder() {
        btnPlaceholder.classList.add('active');
        btnPlaceholder.setAttribute('aria-pressed', 'true');
        btnPreview.classList.remove('active');
        btnPreview.setAttribute('aria-pressed', 'false');
        wrap.style.display = '';
        previewDiv.style.display = 'none';
        previewDiv.setAttribute('aria-hidden', 'true');
    }

    function showPreview() {
        var text = (ta.value || '').trim();
        if (text === '') {
            previewDiv.textContent = '';
        } else {
            var fd = new FormData();
            fd.set('action', 'preview');
            fd.set('text', text);
            fd.set('id', document.getElementById('editId').value || '0');
            var weckzeit = document.getElementById('editWeckzeit').value;
            if (weckzeit) fd.set('weckzeit', weckzeit);
            previewDiv.textContent = 'Wird geladen…';
            fetch(API_WECKER, { method: 'POST', body: fd })
                .then(function(r) { return r.json(); })
                .then(function(data) {
                    previewDiv.textContent = data.ok && data.preview !== undefined ? data.preview : (data.error || 'Fehler');
                })
                .catch(function() { previewDiv.textContent = 'Fehler beim Laden der Vorschau.'; });
        }
        btnPlaceholder.classList.remove('active');
        btnPlaceholder.setAttribute('aria-pressed', 'false');
        btnPreview.classList.add('active');
        btnPreview.setAttribute('aria-pressed', 'true');
        wrap.style.display = 'none';
        previewDiv.style.display = 'block';
        previewDiv.setAttribute('aria-hidden', 'false');
    }

    btnPlaceholder.addEventListener('click', function() { showPlaceholder(); });
    btnPreview.addEventListener('click', function() { showPreview(); });

    window.sprachtextToggleShowPlaceholder = showPlaceholder;
}

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') === 'settingsTabInfo') loadInfoTab();
            if (btn.getAttribute('data-tab') === 'settingsTabUpdate') loadUpdateTab();
            if (btn.getAttribute('data-tab') === 'settingsTabWlan') wlanLoadStatus();
        });
    });

    var btnCronRunOnce = document.getElementById('btnCronRunOnce');
    if (btnCronRunOnce) {
        btnCronRunOnce.addEventListener('click', function() {
            var btn = this;
            var origText = btn.textContent;
            btn.disabled = true;
            btn.textContent = 'Läuft…';
            fetch(API_INFO + '?action=run').then(function(r) { return r.json(); }).then(function(data) {
                btn.disabled = false;
                btn.textContent = origText;
                if (data && data.ok) {
                    var msg = document.getElementById('settingsMsg');
                    if (msg) { msg.textContent = data.message || 'Cron einmal ausgeführt.'; msg.className = 'settings-msg'; }
                    loadInfoTab();
                } else {
                    var msg = document.getElementById('settingsMsg');
                    if (msg) { msg.textContent = data && data.message ? data.message : 'Cron-Ausführung fehlgeschlagen.'; msg.className = 'settings-msg error'; }
                }
            }).catch(function() {
                btn.disabled = false;
                btn.textContent = origText;
                var msg = document.getElementById('settingsMsg');
                if (msg) { msg.textContent = 'Cron-Ausführung fehlgeschlagen.'; msg.className = 'settings-msg error'; }
            });
        });
    }

    var btnResetCache = document.getElementById('btnResetCache');
    if (btnResetCache) {
        btnResetCache.addEventListener('click', function() {
            var btn = this;
            var origText = btn.textContent;
            btn.disabled = true;
            btn.textContent = 'Wird geleert…';
            fetch(API_INFO + '?action=reset_cache').then(function(r) { return r.json(); }).then(function(data) {
                btn.disabled = false;
                btn.textContent = origText;
                var msg = document.getElementById('settingsMsg');
                if (msg) {
                    msg.textContent = (data && data.ok) ? (data.message || 'Cache wurde zurückgesetzt.') : (data && data.message ? data.message : 'Cache zurücksetzen fehlgeschlagen.');
                    msg.className = 'settings-msg' + (data && data.ok ? '' : ' error');
                }
            }).catch(function() {
                btn.disabled = false;
                btn.textContent = origText;
                var msg = document.getElementById('settingsMsg');
                if (msg) { msg.textContent = 'Cache zurücksetzen fehlgeschlagen.'; msg.className = 'settings-msg error'; }
            });
        });
    }

    var btnCleanupScan = document.getElementById('btnCleanupScan');
    if (btnCleanupScan) {
        btnCleanupScan.addEventListener('click', function() { loadCleanupScan(); });
    }

    var btnReboot = document.getElementById('btnReboot');
    if (btnReboot) {
        btnReboot.addEventListener('click', function() { systemCommand('reboot'); });
    }
    var btnShutdown = document.getElementById('btnShutdown');
    if (btnShutdown) {
        btnShutdown.addEventListener('click', function() { systemCommand('shutdown'); });
    }

    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();
    initSprachtextToggle();
    initAudioSearch();
    initTouchKeyboard();
    initWochentageToggle();

    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() {
    var cached = updateCacheGet();
    if (cached) {
        _evalUpdateNotification(cached.current, cached.list);
        return;
    }
    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];
        updateCacheSet(current, list);
        _evalUpdateNotification(current, list);
    }).catch(function() {});
}
function _evalUpdateNotification(current, list) {
    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();
    }
}

function showUpdateToast() {
    var bar = document.getElementById('updateBar');
    if (bar) bar.classList.add('visible');
}

function hideUpdateToast() {
    var bar = document.getElementById('updateBar');
    if (bar) bar.classList.remove('visible');
}

var audioSearchSongs = [];
var audioSearchPlaylists = [];

function loadAudioSearchData(cb) {
    if (window.audioSearchDataLoaded) { if (cb) cb(); return; }
    Promise.all([
        fetch(API_MUSIK + '?action=list').then(function(r) { return r.json(); }),
        fetch(API_MUSIK + '?action=alben_list').then(function(r) { return r.json(); })
    ]).then(function(results) {
        var listData = results[0].ok && results[0].files ? results[0].files : [];
        var albenData = results[1].ok && results[1].alben ? results[1].alben : [];
        audioSearchSongs = listData.map(function(f) {
            var label = (f.titel && f.titel.trim()) ? f.titel.trim() : f.name;
            return { type: 'song', id: f.name, label: label };
        });
        audioSearchPlaylists = albenData.map(function(a) {
            return { type: 'playlist', id: a.id, label: a.name };
        });
        window.audioSearchDataLoaded = true;
        if (cb) cb();
    }).catch(function() { if (cb) cb(); });
}

function filterAudioSearch(query, position) {
    var q = (query || '').trim().toLowerCase();
    if (!q) return [];
    var out = [];
    audioSearchPlaylists.forEach(function(p) {
        if (p.label.toLowerCase().indexOf(q) !== -1) out.push(p);
    });
    audioSearchSongs.forEach(function(s) {
        if (s.label.toLowerCase().indexOf(q) !== -1) out.push(s);
    });
    return out.slice(0, 15);
}

function showAudioSearchResults(inputEl, position) {
    var query = (inputEl.value || '').trim();
    var resultsUl = document.getElementById('audioSearchResults' + (position === 'gleichzeitig' ? 'Gleichzeitig' : 'Danach'));
    if (!resultsUl) return;
    var results = filterAudioSearch(query, position);
    resultsUl.innerHTML = '';
    resultsUl.setAttribute('aria-hidden', 'true');
    if (results.length === 0) { resultsUl.style.display = 'none'; return; }
    results.forEach(function(item) {
        var li = document.createElement('li');
        li.innerHTML = '<span class="audio-search-type">' + (item.type === 'playlist' ? 'Playlist' : 'Lied') + '</span> ' + escapeHtml(item.label);
        li.addEventListener('click', function() {
            addAudioFromSearch(position, item);
            inputEl.value = '';
            resultsUl.innerHTML = '';
            resultsUl.setAttribute('aria-hidden', 'true');
            resultsUl.style.display = 'none';
        });
        resultsUl.appendChild(li);
    });
    resultsUl.setAttribute('aria-hidden', 'false');
    resultsUl.style.display = 'block';
}

function addAudioFromSearch(position, item) {
    var datei = item.type === 'playlist' ? ('playlist:' + item.id) : item.id;
    var titel = item.label || (item.type === 'playlist' ? ('Playlist ' + item.id) : item.id);
    audioList.push({ datei: datei, titel: titel, position: position });
    renderAudioLists();
}

function renderAudioLists() {
    ['gleichzeitig', 'danach'].forEach(function(pos) {
        var ul = document.getElementById('audioList' + pos.charAt(0).toUpperCase() + pos.slice(1));
        if (!ul) return;
        ul.innerHTML = '';
        audioList.filter(function(a) { return a.position === pos; }).forEach(function(a, i) {
            var idx = audioList.indexOf(a);
            var li = document.createElement('li');
            var label = (a.titel && a.titel.trim()) ? a.titel : (a.datei.indexOf('playlist:') === 0 ? ('Playlist ' + a.datei.slice(9)) : a.datei);
            li.innerHTML = '<span title="' + escapeHtml(a.datei) + '">' + escapeHtml(label) + '</span><button type="button" onclick="removeAudio(' + idx + ')">Entfernen</button>';
            ul.appendChild(li);
        });
    });
    var editAudioEl = document.getElementById('editAudio');
    if (editAudioEl) editAudioEl.value = JSON.stringify(audioList);
}

function initAudioSearch() {
    ['gleichzeitig', 'danach'].forEach(function(pos) {
        var inputId = 'audioSearch' + (pos === 'gleichzeitig' ? 'Gleichzeitig' : 'Danach');
        var input = document.getElementById(inputId);
        var resultsId = 'audioSearchResults' + (pos === 'gleichzeitig' ? 'Gleichzeitig' : 'Danach');
        if (!input) return;
        input.addEventListener('focus', function() {
            loadAudioSearchData(function() {
                showAudioSearchResults(input, pos);
            });
        });
        input.addEventListener('input', function() {
            loadAudioSearchData(function() { showAudioSearchResults(input, pos); });
        });
        input.addEventListener('blur', function() {
            setTimeout(function() {
                var ul = document.getElementById(resultsId);
                if (ul) { ul.innerHTML = ''; ul.setAttribute('aria-hidden', 'true'); ul.style.display = 'none'; }
            }, 200);
        });
    });
    document.addEventListener('click', function(e) {
        if (e.target.classList && e.target.classList.contains('audio-search-input')) return;
        if (e.target.closest && e.target.closest('.audio-search-results')) return;
        ['Gleichzeitig', 'Danach'].forEach(function(suffix) {
            var ul = document.getElementById('audioSearchResults' + suffix);
            if (ul) { ul.innerHTML = ''; ul.setAttribute('aria-hidden', 'true'); ul.style.display = 'none'; }
        });
    });
}

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() {
    var editOverlay = document.getElementById('editOverlay');
    if (editOverlay && editOverlay.style.display === 'flex') return;
    fetch(API_WECKER + '?action=list').then(function(r) { return r.json(); }).then(function(data) {
        if (!data.ok) return;
        var newHash = JSON.stringify(data.wecker || []);
        if (newHash === _weckerLiveHash) return;
        _weckerLiveHash = newHash;
        weckerListeData = data.wecker || [];
        renderWeckerListe(weckerListeData);
        updateHeader();
    }).catch(function() {});
}
setInterval(refreshWeckerData, 1000);

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;
            });
            syncWochentageAktivKlasse();
            toggleWochentageVisibility();
            audioList = Array.isArray(w.audio) ? w.audio.map(function(a) {
                var pos = a.position === 'gleichzeitig' || a.position === 'danach' ? a.position : 'danach';
                return { datei: a.datei, titel: a.titel || null, position: pos };
            }) : [];
            renderAudioLists();
            document.getElementById('btnDeleteWecker').style.display = '';
            document.getElementById('editOverlay').style.display = 'flex';
            if (typeof window.sprachtextToggleShowPlaceholder === 'function') window.sprachtextToggleShowPlaceholder();
        })
        .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; });
    syncWochentageAktivKlasse();
    audioList = [];
    renderAudioLists();
    toggleWochentageVisibility();
    document.getElementById('btnDeleteWecker').style.display = 'none';
    document.getElementById('editOverlay').style.display = 'flex';
    if (typeof window.sprachtextToggleShowPlaceholder === 'function') window.sprachtextToggleShowPlaceholder();
}

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) {
                weckerListeData = weckerListeData.map(function(w) { return w.id == id ? Object.assign({}, w, { aktiv: aktiv ? 1 : 0 }) : w; });
                _weckerLiveHash = JSON.stringify(weckerListeData);
                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();
            _weckerLiveHash = null;
            refreshWeckerData();
        })
        .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();
            _weckerLiveHash = null;
            refreshWeckerData();
        })
        .catch(() => alert('Fehler beim Löschen'));
}

var SETTINGS_LS_KEY = 'wecker-settings-cache';
var SETTINGS_LS_TTL = 5 * 60 * 1000;
function settingsCacheGet() {
    try {
        var raw = localStorage.getItem(SETTINGS_LS_KEY);
        if (!raw) return null;
        var c = JSON.parse(raw);
        if (!c || Date.now() - c.ts > SETTINGS_LS_TTL) return null;
        return c.data;
    } catch (e) { return null; }
}
function settingsCacheSet(data) {
    try { localStorage.setItem(SETTINGS_LS_KEY, JSON.stringify({ ts: Date.now(), data: data })); } catch (e) {}
}

function fillSettingsForm(e) {
    document.getElementById('settingTtsName').value = e.tts_name || '';
    document.getElementById('settingTtsProvider').value = (e.tts_provider === 'openai' ? 'openai' : 'piper');
    document.getElementById('settingTtsModell').value = e.tts_modell || 'de_DE-thorsten-high';
    document.getElementById('settingTtsOpenaiApiKey').value = e.tts_openai_api_key || '';
    document.getElementById('settingTtsOpenaiModel').value = e.tts_openai_model || 'tts-1';
    document.getElementById('settingTtsOpenaiVoice').value = e.tts_openai_voice || 'alloy';
    updateTtsProviderVisibility();
    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 || '';
}

function openSettings() {
    if (sleepModeVisible) hideSleepMode();
    var overlay = document.getElementById('settingsOverlay');
    var loading = document.getElementById('settingsOverlayLoading');
    overlay.style.display = 'flex';
    document.getElementById('settingsMsg').textContent = '';
    showSettingsTab('settingsTabAllgemein');
    var cached = settingsCacheGet();
    if (cached) {
        fillSettingsForm(cached);
        if (loading) loading.style.display = 'none';
    } else {
        if (loading) loading.style.display = 'flex';
    }
    fetch(API_EINSTELLUNGEN)
        .then(r => r.json())
        .then(data => {
            if (loading) loading.style.display = 'none';
            if (!data.ok) return;
            const e = data.einstellungen || {};
            settingsCacheSet(e);
            fillSettingsForm(e);
        })
        .catch(function() {
            if (loading) loading.style.display = 'none';
            if (!cached) {
                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 updateTtsProviderVisibility() {
    var provider = (document.getElementById('settingTtsProvider') || {}).value || 'piper';
    var piperEl = document.getElementById('ttsPiperOptions');
    var openaiEl = document.getElementById('ttsOpenaiOptions');
    if (piperEl) piperEl.style.display = provider === 'piper' ? '' : 'none';
    if (openaiEl) openaiEl.style.display = provider === 'openai' ? '' : 'none';
}
var settingTtsProviderEl = document.getElementById('settingTtsProvider');
if (settingTtsProviderEl) settingTtsProviderEl.addEventListener('change', updateTtsProviderVisibility);

(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_provider', document.getElementById('settingTtsProvider').value);
    fd.set('tts_modell', document.getElementById('settingTtsModell').value);
    fd.set('tts_openai_api_key', document.getElementById('settingTtsOpenaiApiKey').value);
    fd.set('tts_openai_model', document.getElementById('settingTtsOpenaiModel').value);
    fd.set('tts_openai_voice', document.getElementById('settingTtsOpenaiVoice').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 newOrt = (document.getElementById('settingWetterOrt').value || '').trim();
                try {
                    var oldOrt = localStorage.getItem(WETTER_ORT_LS_KEY) || '';
                    if (newOrt !== oldOrt) { wetterCacheClear(); currentWetterOrt = newOrt; }
                    localStorage.setItem(WETTER_ORT_LS_KEY, newOrt);
                } catch (e) {}
                settingsCacheSet({
                    tts_name: document.getElementById('settingTtsName').value,
                    tts_provider: document.getElementById('settingTtsProvider').value,
                    tts_modell: document.getElementById('settingTtsModell').value,
                    tts_openai_api_key: '********',
                    tts_openai_model: document.getElementById('settingTtsOpenaiModel').value,
                    tts_openai_voice: document.getElementById('settingTtsOpenaiVoice').value,
                    lautstaerke: document.getElementById('settingLautstaerke').value,
                    lautstaerke_musik: document.getElementById('settingLautstaerkeMusik').value,
                    musik_ducking_prozent: document.getElementById('settingMusikDucking').value,
                    zeitzone: document.getElementById('settingZeitzone').value,
                    wetter_ort: newOrt,
                    sleep_mode_sekunden: document.getElementById('settingSleepModeSekunden').value,
                    accent_color: (document.getElementById('settingAccentColorHex').value || '').trim(),
                });
                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 = '';
var WETTER_LS_KEY = 'wecker-wetter-data';
var WETTER_ORT_LS_KEY = 'wecker-wetter-ort';
var WETTER_LS_TTL = 15 * 60 * 1000;
function wetterCacheGet(ort) {
    try {
        var raw = localStorage.getItem(WETTER_LS_KEY);
        if (!raw) return null;
        var c = JSON.parse(raw);
        if (!c || c.ort !== ort) return null;
        if (Date.now() - c.ts > WETTER_LS_TTL) return null;
        return c.data;
    } catch (e) { return null; }
}
function wetterCacheSet(ort, data) {
    try {
        localStorage.setItem(WETTER_LS_KEY, JSON.stringify({ ort: ort, ts: Date.now(), data: data }));
        localStorage.setItem(WETTER_ORT_LS_KEY, ort);
    } catch (e) {}
}
function wetterCacheClear() {
    try { localStorage.removeItem(WETTER_LS_KEY); } catch (e) {}
}
function loadWetterFromStorage() {
    var subtitle = document.getElementById('wetterSubtitle');
    var leftEl = document.getElementById('wetterAktuell');
    var centerEl = document.getElementById('wetterForecast');
    var rightEl = document.getElementById('wetterDetails');
    var cachedOrt = '';
    try { cachedOrt = localStorage.getItem(WETTER_ORT_LS_KEY) || ''; } catch (e) {}
    if (cachedOrt) {
        currentWetterOrt = cachedOrt;
        if (subtitle) subtitle.textContent = cachedOrt;
        fillWetterPanels(leftEl, centerEl, rightEl, cachedOrt);
    } else {
        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) {
        var ort = (data.einstellungen && data.einstellungen.wetter_ort) ? String(data.einstellungen.wetter_ort).trim() : '';
        try { localStorage.setItem(WETTER_ORT_LS_KEY, ort); } catch (e) {}
        if (ort !== currentWetterOrt) {
            currentWetterOrt = ort;
            if (subtitle) subtitle.textContent = ort || 'Ort in Einstellungen festlegen';
            fillWetterPanels(leftEl, centerEl, rightEl, ort);
        } else if (!cachedOrt) {
            currentWetterOrt = ort;
            if (subtitle) subtitle.textContent = ort || 'Ort in Einstellungen festlegen';
            fillWetterPanels(leftEl, centerEl, rightEl, ort);
        }
    }).catch(function() {
        if (!cachedOrt && 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() {
        var ort = currentWetterOrt;
        if (!ort) { loadWetterFromStorage(); return; }
        fillWetterPanels(
            document.getElementById('wetterAktuell'),
            document.getElementById('wetterForecast'),
            document.getElementById('wetterDetails'),
            ort, true
        );
    });
}

function loadInfoTab() {
    var ipEl = document.getElementById('infoIp');
    var timeEl = document.getElementById('infoCronTime');
    var statusEl = document.getElementById('infoCronStatus');
    if (ipEl) ipEl.textContent = '…';
    if (timeEl) timeEl.textContent = '…';
    if (statusEl) { statusEl.textContent = ''; statusEl.className = 'info-cron-status'; }
    fetch(API_INFO).then(function(r) { return r.json(); }).then(function(data) {
        if (!data || !data.ok) return;
        if (ipEl) ipEl.textContent = (data.ip && data.ip !== '') ? data.ip : '–';
        var last = data.cron_last_activity || null;
        if (timeEl) {
            if (last) {
                try {
                    var d = new Date(last);
                    timeEl.textContent = isNaN(d.getTime()) ? last : d.toLocaleString('de-DE');
                } catch (e) { timeEl.textContent = last; }
            } else timeEl.textContent = '–';
        }
        if (statusEl) {
            var status = data.cron_status || 'error';
            statusEl.classList.remove('status-ok', 'status-warn', 'status-error');
            statusEl.classList.add('status-' + status);
            if (status === 'ok') statusEl.textContent = 'Cron läuft (letzter Lauf vor weniger als 1 Minute)';
            else if (status === 'warn') statusEl.textContent = 'Cron war vor 1–5 Minuten aktiv';
            else statusEl.textContent = 'Cron seit über 5 Minuten inaktiv oder nicht gestartet';
        }
    }).catch(function() {
        if (ipEl) ipEl.textContent = '–';
        if (timeEl) timeEl.textContent = 'Fehler';
        if (statusEl) { statusEl.className = 'info-cron-status status-error'; statusEl.textContent = 'Info konnte nicht geladen werden.'; }
    });
    loadCronJobList();
}

function loadCronJobList() {
    var listEl = document.getElementById('cronJobList');
    var hintEl = document.getElementById('cronJobListHint');
    if (!listEl) return;
    if (hintEl) hintEl.textContent = 'Lade Jobs…';
    fetch(API_INFO + '?action=list_jobs').then(function(r) { return r.json(); }).then(function(data) {
        if (hintEl) hintEl.remove();
        listEl.innerHTML = '';
        if (!data || !data.ok || !data.jobs || data.jobs.length === 0) {
            listEl.innerHTML = '<p class="platzhalter-hint">Keine Jobs gefunden.</p>';
            return;
        }
        data.jobs.forEach(function(job) {
            var item = document.createElement('div');
            item.className = 'cron-job-item';
            var meta = '';
            if (job.letzte_ausfuehrung) {
                try {
                    var d = new Date(job.letzte_ausfuehrung);
                    meta = 'Zuletzt: ' + (isNaN(d.getTime()) ? job.letzte_ausfuehrung : d.toLocaleString('de-DE'));
                } catch(e) { meta = 'Zuletzt: ' + job.letzte_ausfuehrung; }
            } else {
                meta = 'Noch nie ausgeführt';
            }
            if (job.interval_sekunden) {
                meta += ' · Intervall: ' + job.interval_sekunden + ' s';
            }
            item.innerHTML =
                '<div class="cron-job-info">' +
                    '<div class="cron-job-label">' + escapeHtml(job.label) + '</div>' +
                    '<div class="cron-job-meta" id="cronMeta_' + escapeHtml(job.name) + '">' + escapeHtml(meta) + '</div>' +
                '</div>' +
                '<button type="button" class="cron-job-run-btn" data-job="' + escapeHtml(job.name) + '">Starten</button>';
            listEl.appendChild(item);
        });
        listEl.querySelectorAll('.cron-job-run-btn').forEach(function(btn) {
            btn.addEventListener('click', function() {
                runSingleCronJob(btn.getAttribute('data-job'), btn);
            });
        });
    }).catch(function() {
        listEl.innerHTML = '<p class="platzhalter-hint">Jobs konnten nicht geladen werden.</p>';
    });
}

function runSingleCronJob(jobName, btn) {
    var origText = btn ? btn.textContent : '';
    if (btn) { btn.disabled = true; btn.textContent = 'Läuft…'; }
    fetch(API_INFO + '?action=run_job&job=' + encodeURIComponent(jobName))
        .then(function(r) { return r.json(); })
        .then(function(data) {
            if (btn) { btn.disabled = false; btn.textContent = origText; }
            var msg = document.getElementById('settingsMsg');
            if (msg) {
                msg.textContent = (data && data.ok) ? (data.message || 'Job ausgeführt.') : (data && data.message ? data.message : 'Fehler.');
                msg.className = 'settings-msg' + (data && data.ok ? '' : ' error');
            }
            if (data && data.ok) loadInfoTab();
        })
        .catch(function() {
            if (btn) { btn.disabled = false; btn.textContent = origText; }
            var msg = document.getElementById('settingsMsg');
            if (msg) { msg.textContent = 'Fehler beim Starten des Jobs.'; msg.className = 'settings-msg error'; }
        });
}

function formatBytes(b) {
    if (b === 0) return '0 B';
    if (b < 1024) return b + ' B';
    if (b < 1024 * 1024) return (b / 1024).toFixed(1) + ' KB';
    return (b / 1024 / 1024).toFixed(1) + ' MB';
}

function loadCleanupScan() {
    var resultEl = document.getElementById('cleanupResult');
    var btn = document.getElementById('btnCleanupScan');
    if (!resultEl) return;
    if (btn) { btn.disabled = true; btn.textContent = 'Scannt…'; }
    resultEl.style.display = 'block';
    resultEl.innerHTML = '<p class="platzhalter-hint">Analysiere…</p>';
    fetch(API_INFO + '?action=cleanup_scan')
        .then(function(r) { return r.json(); })
        .then(function(data) {
            if (btn) { btn.disabled = false; btn.textContent = 'Neu scannen'; }
            if (!data.ok) { resultEl.innerHTML = '<p class="platzhalter-hint" style="color:var(--danger,#e74c3c)">Fehler beim Scannen.</p>'; return; }
            renderCleanupResult(resultEl, data.items);
        })
        .catch(function() {
            if (btn) { btn.disabled = false; btn.textContent = 'Scannen'; }
            resultEl.innerHTML = '<p class="platzhalter-hint" style="color:var(--danger,#e74c3c)">Netzwerkfehler.</p>';
        });
}

function renderCleanupResult(el, items) {
    var backups = items.backups || {};
    var logs = items.logs || {};
    var zip = items.update_zip || {};
    var wetter = items.wetter_cache || {};
    var rows = '';

    // Backups
    var bCount = backups.count || 0;
    var bSize = backups.size || 0;
    rows += '<div class="cleanup-item">' +
        '<div class="cleanup-item-info">' +
        '<div class="cleanup-item-label">Backups (backup_*)</div>' +
        '<div class="cleanup-item-meta">' + bCount + ' Ordner &middot; ' + formatBytes(bSize) + '</div>' +
        '</div><div class="cleanup-item-actions">';
    if (bCount > 1) {
        rows += '<button class="cleanup-btn" onclick="runCleanupTarget(\'backups_except_newest\',this)">Älteste löschen</button>';
    }
    if (bCount > 0) {
        rows += '<button class="cleanup-btn danger" onclick="runCleanupTarget(\'backups_all\',this)">Alle löschen</button>';
    }
    if (bCount === 0) rows += '<span style="font-size:0.82rem;color:var(--text-muted)">–</span>';
    rows += '</div></div>';

    // Logs
    var lCount = logs.count || 0;
    var lSize = logs.size || 0;
    rows += '<div class="cleanup-item">' +
        '<div class="cleanup-item-info">' +
        '<div class="cleanup-item-label">Log-Dateien</div>' +
        '<div class="cleanup-item-meta">' + lCount + ' Datei(en) &middot; ' + formatBytes(lSize) + '</div>' +
        '</div><div class="cleanup-item-actions">';
    if (lCount > 0) {
        rows += '<button class="cleanup-btn" onclick="runCleanupTarget(\'logs\',this)">Löschen</button>';
    } else {
        rows += '<span style="font-size:0.82rem;color:var(--text-muted)">–</span>';
    }
    rows += '</div></div>';

    // Update-ZIP
    var zipExists = zip.exists || false;
    var zipSize = zip.size || 0;
    rows += '<div class="cleanup-item">' +
        '<div class="cleanup-item-info">' +
        '<div class="cleanup-item-label">update.zip</div>' +
        '<div class="cleanup-item-meta">' + (zipExists ? formatBytes(zipSize) : 'nicht vorhanden') + '</div>' +
        '</div><div class="cleanup-item-actions">';
    if (zipExists) {
        rows += '<button class="cleanup-btn" onclick="runCleanupTarget(\'update_zip\',this)">Löschen</button>';
    } else {
        rows += '<span style="font-size:0.82rem;color:var(--text-muted)">–</span>';
    }
    rows += '</div></div>';

    // Wetter-Cache
    var wCount = wetter.count || 0;
    var wSize = wetter.size || 0;
    rows += '<div class="cleanup-item">' +
        '<div class="cleanup-item-info">' +
        '<div class="cleanup-item-label">Wetter-Cache</div>' +
        '<div class="cleanup-item-meta">' + wCount + ' Datei(en) &middot; ' + formatBytes(wSize) + '</div>' +
        '</div><div class="cleanup-item-actions">';
    if (wCount > 0) {
        rows += '<button class="cleanup-btn" onclick="runCleanupTarget(\'wetter_cache\',this)">Leeren</button>';
    } else {
        rows += '<span style="font-size:0.82rem;color:var(--text-muted)">–</span>';
    }
    rows += '</div></div>';

    el.innerHTML = '<div class="cleanup-list">' + rows + '</div>' +
        '<div id="cleanupLog" class="cleanup-log" style="display:none;"></div>';
}

function runCleanupTarget(target, btn) {
    var logEl = document.getElementById('cleanupLog');
    if (btn) { btn.disabled = true; btn.textContent = 'Läuft…'; }
    var fd = new FormData();
    fd.append('target', target);
    fetch(API_INFO + '?action=cleanup_run', { method: 'POST', body: fd })
        .then(function(r) { return r.json(); })
        .then(function(data) {
            if (btn) { btn.disabled = false; btn.textContent = btn.textContent.replace('Läuft…', btn._origText || 'Fertig'); }
            if (logEl) {
                logEl.style.display = 'block';
                var lines = (data.log && Array.isArray(data.log)) ? data.log : [data.message || (data.ok ? 'Fertig.' : 'Fehler.')];
                logEl.textContent = lines.join('\n');
                logEl.style.color = data.ok ? 'var(--text-muted)' : 'var(--danger,#e74c3c)';
            }
            if (data.ok) loadCleanupScan();
        })
        .catch(function() {
            if (btn) { btn.disabled = false; }
            if (logEl) { logEl.style.display = 'block'; logEl.textContent = 'Netzwerkfehler.'; logEl.style.color = 'var(--danger,#e74c3c)'; }
        });
}

function systemCommand(cmd) {
    var msgEl = document.getElementById('systemCmdMsg');
    var btnR = document.getElementById('btnReboot');
    var btnS = document.getElementById('btnShutdown');
    var label = cmd === 'reboot' ? 'Neu starten' : 'Herunterfahren';
    if (!confirm(label + ': Wirklich fortfahren?')) return;
    if (btnR) btnR.disabled = true;
    if (btnS) btnS.disabled = true;
    if (msgEl) { msgEl.style.display = 'block'; msgEl.textContent = label + ' wird ausgeführt…'; msgEl.style.color = ''; }
    var fd = new FormData();
    fd.append('cmd', cmd);
    fetch(API_INFO + '?action=system_cmd', { method: 'POST', body: fd })
        .then(function(r) { return r.json(); })
        .then(function(data) {
            if (btnR) btnR.disabled = false;
            if (btnS) btnS.disabled = false;
            if (msgEl) {
                msgEl.textContent = data.message || (data.ok ? 'Befehl ausgeführt.' : 'Fehler.');
                msgEl.style.color = data.ok ? '' : 'var(--danger,#e74c3c)';
            }
        })
        .catch(function() {
            if (btnR) btnR.disabled = false;
            if (btnS) btnS.disabled = false;
            if (msgEl) { msgEl.textContent = 'Netzwerkfehler.'; msgEl.style.color = 'var(--danger,#e74c3c)'; }
        });
}

var UPDATE_LS_KEY = 'wecker-update-cache';
var UPDATE_LS_TTL = 20 * 60 * 1000;
function updateCacheGet() {
    try {
        var raw = localStorage.getItem(UPDATE_LS_KEY);
        if (!raw) return null;
        var c = JSON.parse(raw);
        if (!c || Date.now() - c.ts > UPDATE_LS_TTL) return null;
        return c;
    } catch (e) { return null; }
}
function updateCacheSet(current, list) {
    try { localStorage.setItem(UPDATE_LS_KEY, JSON.stringify({ ts: Date.now(), current: current, list: list })); } catch (e) {}
}
function updateCacheClear() {
    try { localStorage.removeItem(UPDATE_LS_KEY); } catch (e) {}
}

function fillUpdateTab(current, list) {
    var currentEl = document.getElementById('updateCurrentVersion');
    var selectEl = document.getElementById('updateVersionSelect');
    var hintEl = document.getElementById('updateVersionHint');
    var installBtn = document.getElementById('updateInstallBtn');
    if (!selectEl || !installBtn) return;
    if (currentEl) currentEl.textContent = (current && current.ok && current.version != null) ? current.version : '–';
    var versionen = (list && list.ok && list.versionen) ? list.versionen : [];
    selectEl.innerHTML = '';
    if (versionen.length === 0) {
        selectEl.innerHTML = '<option value="">Keine Versionen geladen</option>';
        installBtn.disabled = true;
    } 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;
    }
    selectEl.removeEventListener('change', selectEl._hintHandler);
    selectEl._hintHandler = function() {
        var opt = selectEl.options[selectEl.selectedIndex];
        var commit = opt && opt.getAttribute('data-commit');
        if (hintEl) hintEl.textContent = commit ? 'Commit: ' + commit : '';
    };
    selectEl.addEventListener('change', selectEl._hintHandler);
    selectEl._hintHandler();
}

function loadUpdateTab() {
    var selectEl = document.getElementById('updateVersionSelect');
    var installBtn = document.getElementById('updateInstallBtn');
    if (!selectEl || !installBtn) return;
    var cached = updateCacheGet();
    if (cached) {
        fillUpdateTab(cached.current, cached.list);
    } else {
        selectEl.innerHTML = '<option value="">Lade…</option>';
        installBtn.disabled = true;
        var currentEl = document.getElementById('updateCurrentVersion');
        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];
        updateCacheSet(current, list);
        fillUpdateTab(current, list);
    }).catch(function() {
        if (!cached) {
            var currentEl = document.getElementById('updateCurrentVersion');
            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) {
                    updateCacheClear();
                    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 '/assets/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, force) {
    if (!leftEl && !centerEl && !rightEl) return;
    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 (!force) {
        var cached = wetterCacheGet(ort);
        if (cached) {
            fillWetterPanelsFromData(leftEl, centerEl, rightEl, cached);
            return;
        }
    }
    var loading = '<p class="sleep-wetter-tag">Lade…</p>';
    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;
        }
        wetterCacheSet(ort, data);
        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;
var sleepModeTimeInterval = null;

function updateSleepModeTime() {
    var clockEl = document.getElementById('sleepModeTimeClock');
    var dateEl = document.getElementById('sleepModeTimeDate');
    if (!clockEl && !dateEl) return;
    var now = new Date();
    var h = now.getHours(), m = now.getMinutes(), s = now.getSeconds();
    if (clockEl) clockEl.textContent = (h < 10 ? '0' : '') + h + ':' + (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s;
    if (dateEl) dateEl.textContent = now.toLocaleDateString('de-DE', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' });
}

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

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

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

function isSettingsOverlayOpen() {
    var el = document.getElementById('settingsOverlay');
    return el && el.style.display === 'flex';
}

function checkSleepModeTimer() {
    if (!isSleepModeHostErlaubt()) return;
    if (sleepModeSekunden <= 0) return;
    if (sleepModeVisible) return;
    if (isSettingsOverlayOpen()) 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;

/* ── WLAN ──────────────────────────────────────────────────────────────── */
var _wlanConnectSsid = '';
var _wlanConnectOpen = false;

function wlanLoadStatus() {
    var box = document.getElementById('wlanStatus');
    if (box) box.innerHTML = '<span class="wlan-status-text">Wird geladen…</span>';
    fetch(API_WLAN + '?action=status')
        .then(function(r) { return r.json(); })
        .then(function(data) {
            if (!data.ok) { wlanStatusError(data.error || 'Fehler'); return; }
            var btn = document.getElementById('wlanDisconnectBtn');
            if (btn) btn.style.display = data.connected ? 'inline-block' : 'none';
            if (!box) return;
            if (data.connected) {
                box.innerHTML =
                    '<span class="wlan-status-dot connected"></span>' +
                    '<div class="wlan-status-info">' +
                    '<span class="wlan-status-ssid">' + escapeHtml(data.ssid || '–') + '</span>' +
                    (data.ip ? '<span class="wlan-status-ip">' + escapeHtml(data.ip) + '</span>' : '') +
                    (data.signal ? '<span class="wlan-status-ip">Signal: ' + data.signal + '%</span>' : '') +
                    '</div>';
            } else {
                box.innerHTML =
                    '<span class="wlan-status-dot disconnected"></span>' +
                    '<div class="wlan-status-info"><span class="wlan-status-ssid">Nicht verbunden</span></div>';
            }
        })
        .catch(function() { wlanStatusError('Netzwerkfehler'); });
}

function wlanStatusError(msg) {
    var box = document.getElementById('wlanStatus');
    if (box) box.innerHTML = '<span class="wlan-status-dot disconnected"></span>' +
        '<span class="wlan-status-text" style="color:var(--text-muted)">' + escapeHtml(msg) + '</span>';
}

function wlanScan() {
    var btn = document.getElementById('wlanScanBtn');
    if (btn) { btn.disabled = true; btn.textContent = 'Scannen…'; }
    var sec = document.getElementById('wlanNetworkSection');
    var list = document.getElementById('wlanNetworkList');
    if (list) list.innerHTML = '<span style="color:var(--text-muted);font-size:0.85rem">Suche nach Netzwerken…</span>';
    if (sec) sec.style.display = 'block';
    document.getElementById('wlanConnectSection').style.display = 'none';

    fetch(API_WLAN + '?action=scan')
        .then(function(r) { return r.json(); })
        .then(function(data) {
            if (btn) { btn.disabled = false; btn.textContent = 'Netzwerke scannen'; }
            if (!data.ok || !list) { if (list) list.innerHTML = '<span style="color:#ef4444">Scan fehlgeschlagen.</span>'; return; }
            var nets = data.networks || [];
            if (nets.length === 0) { list.innerHTML = '<span style="color:var(--text-muted);font-size:0.85rem">Keine Netzwerke gefunden.</span>'; return; }
            list.innerHTML = nets.map(function(n) {
                var badge = n.in_use
                    ? '<span class="wlan-network-badge active">Verbunden</span>'
                    : (n.open ? '<span class="wlan-network-badge open">Offen</span>' : '<span class="wlan-network-badge">🔒</span>');
                return '<div class="wlan-network-item' + (n.in_use ? ' in-use' : '') + '" onclick="wlanPickNetwork(' + JSON.stringify(n.ssid) + ',' + (n.open ? 'true' : 'false') + ',' + (n.in_use ? 'true' : 'false') + ')">' +
                    '<span class="wlan-network-ssid">' + escapeHtml(n.ssid) + '</span>' +
                    '<span class="wlan-network-signal">' + escapeHtml(n.bars || '') + ' ' + n.signal + '%</span>' +
                    badge +
                    '</div>';
            }).join('');
        })
        .catch(function() {
            if (btn) { btn.disabled = false; btn.textContent = 'Netzwerke scannen'; }
            if (list) list.innerHTML = '<span style="color:#ef4444">Netzwerkfehler beim Scannen.</span>';
        });
}

function wlanPickNetwork(ssid, isOpen, isInUse) {
    if (isInUse) return;
    _wlanConnectSsid = ssid;
    _wlanConnectOpen = isOpen;
    var sec = document.getElementById('wlanConnectSection');
    var label = document.getElementById('wlanConnectSsidLabel');
    var pwField = document.getElementById('wlanPassword');
    var msg = document.getElementById('wlanConnectMsg');
    if (label) label.textContent = ssid;
    if (pwField) { pwField.value = ''; pwField.style.display = isOpen ? 'none' : 'block'; }
    if (msg) { msg.style.display = 'none'; msg.textContent = ''; }
    if (sec) { sec.style.display = 'block'; sec.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); }
}

function wlanCancelConnect() {
    document.getElementById('wlanConnectSection').style.display = 'none';
    _wlanConnectSsid = '';
}

function wlanConnect() {
    var ssid = _wlanConnectSsid;
    var password = (document.getElementById('wlanPassword') || {}).value || '';
    var btn = document.getElementById('wlanConnectBtn');
    var msg = document.getElementById('wlanConnectMsg');
    if (!ssid) return;
    if (btn) { btn.disabled = true; btn.textContent = 'Verbinde…'; }
    if (msg) { msg.style.display = 'none'; }
    var body = new URLSearchParams({ ssid: ssid, password: password, open: _wlanConnectOpen ? '1' : '0' });
    fetch(API_WLAN + '?action=connect', { method: 'POST', body: body })
        .then(function(r) { return r.json(); })
        .then(function(data) {
            if (btn) { btn.disabled = false; btn.textContent = 'Verbinden'; }
            if (msg) {
                msg.style.display = 'block';
                msg.className = 'wlan-msg ' + (data.ok ? 'ok' : 'error');
                msg.textContent = data.ok ? (data.msg || 'Verbunden!') : (data.error || 'Fehler');
            }
            if (data.ok) {
                setTimeout(function() {
                    wlanLoadStatus();
                    document.getElementById('wlanConnectSection').style.display = 'none';
                }, 1500);
            }
        })
        .catch(function() {
            if (btn) { btn.disabled = false; btn.textContent = 'Verbinden'; }
            if (msg) { msg.style.display = 'block'; msg.className = 'wlan-msg error'; msg.textContent = 'Netzwerkfehler.'; }
        });
}

function wlanDisconnect() {
    if (!confirm('WLAN wirklich trennen?')) return;
    fetch(API_WLAN + '?action=disconnect', { method: 'POST' })
        .then(function(r) { return r.json(); })
        .then(function(data) {
            setTimeout(wlanLoadStatus, 1000);
        })
        .catch(function() {});
}
</script>

</body>
</html>
