Add comprehensive admin settings control panel

Erweitere Admin-Bereich um umfangreiche Settings-Steuerung:

**Punkt 2 - UI Anzeige:**
- Empfehlungs-Banner ein/aus
- QR-Code Section ein/aus
- Social Media Links ein/aus
- Patrouille Suisse Section ein/aus

**Punkt 3 - Zoom & Timelapse:**
- Zoom-Controls anzeigen/verstecken
- Max Zoom-Level konfigurierbar (1.5x - 4.0x)
- Timelapse Rückwärts-Modus ein/aus

**Punkt 5 - Content Management:**
- Gästebuch aktivieren/deaktivieren
- Galerie aktivieren/deaktivieren
- KI-Events anzeigen/verstecken
- Max Gästebuch-Einträge limit

**Punkt 6 - Technische Settings:**
- Viewer Update-Intervall konfigurierbar
- Session Timeout einstellbar

**Punkt 7 - Theme & Design:**
- Standard-Theme auswählbar (Legacy/Alpine/Modern)
- Theme-Switcher anzeigen/verstecken (war auskommentiert)

**Punkt 8 - SEO & Meta:**
- Custom Title konfigurierbar
- Meta Description editierbar
- Meta Keywords verwaltbar

**Technische Änderungen:**
- SettingsManager.php: Neue Defaults und Helper-Methoden
- Admin-Panel: Neue Settings-Gruppen mit Toggle-Switches
- JavaScript: Live-Apply ohne Reload für alle Settings
- HTML: Sections mit PHP-Settings verbunden
- CSS: Admin-Panel Styling hinzugefügt
- TimelapseController: reverseEnabled Setting integriert
This commit is contained in:
Claude
2026-01-22 17:47:56 +00:00
parent 36558e97cb
commit 0ce527c69e
2 changed files with 600 additions and 21 deletions
+117 -1
View File
@@ -26,7 +26,8 @@ class SettingsManager {
return [ return [
'viewer_display' => [ 'viewer_display' => [
'enabled' => true, 'enabled' => true,
'min_viewers' => 1 'min_viewers' => 1,
'update_interval' => 5 // Sekunden
], ],
'video_mode' => [ 'video_mode' => [
'play_in_player' => true, 'play_in_player' => true,
@@ -36,6 +37,42 @@ class SettingsManager {
'default_speed' => 1, 'default_speed' => 1,
'available_speeds' => [1, 10, 100] 'available_speeds' => [1, 10, 100]
], ],
// Punkt 2: UI-Anzeige Features
'ui_display' => [
'show_recommendation_banner' => true,
'show_qr_code' => true,
'show_social_media' => true,
'show_patrouille_suisse' => true
],
// Punkt 3: Zoom & Timelapse
'zoom_timelapse' => [
'show_zoom_controls' => true,
'max_zoom_level' => 4.0,
'timelapse_reverse_enabled' => true
],
// Punkt 5: Content Management
'content' => [
'guestbook_enabled' => true,
'gallery_enabled' => true,
'ai_events_enabled' => true,
'max_guestbook_entries' => 50
],
// Punkt 6: Technische Settings
'technical' => [
'viewer_update_interval' => 5, // Sekunden
'session_timeout' => 30 // Sekunden
],
// Punkt 7: Theme & Design
'theme' => [
'default_theme' => 'theme-legacy',
'show_theme_switcher' => false
],
// Punkt 8: SEO & Meta
'seo' => [
'custom_title' => '',
'meta_description' => '',
'meta_keywords' => ''
],
'last_updated' => null, 'last_updated' => null,
'updated_by' => null 'updated_by' => null
]; ];
@@ -123,4 +160,83 @@ class SettingsManager {
public function shouldAllowDownload() { public function shouldAllowDownload() {
return $this->get('video_mode.allow_download') === true; return $this->get('video_mode.allow_download') === true;
} }
// UI Display Helper
public function shouldShowRecommendationBanner() {
return $this->get('ui_display.show_recommendation_banner') === true;
}
public function shouldShowQRCode() {
return $this->get('ui_display.show_qr_code') === true;
}
public function shouldShowSocialMedia() {
return $this->get('ui_display.show_social_media') === true;
}
public function shouldShowPatrouillesuisse() {
return $this->get('ui_display.show_patrouille_suisse') === true;
}
// Content Management Helper
public function isGuestbookEnabled() {
return $this->get('content.guestbook_enabled') === true;
}
public function isGalleryEnabled() {
return $this->get('content.gallery_enabled') === true;
}
public function isAIEventsEnabled() {
return $this->get('content.ai_events_enabled') === true;
}
public function getMaxGuestbookEntries() {
return $this->get('content.max_guestbook_entries') ?? 50;
}
// Theme Helper
public function getDefaultTheme() {
return $this->get('theme.default_theme') ?? 'theme-legacy';
}
public function shouldShowThemeSwitcher() {
return $this->get('theme.show_theme_switcher') === true;
}
// Technical Helper
public function getViewerUpdateInterval() {
return $this->get('technical.viewer_update_interval') ?? 5;
}
public function getSessionTimeout() {
return $this->get('technical.session_timeout') ?? 30;
}
// Zoom & Timelapse Helper
public function shouldShowZoomControls() {
return $this->get('zoom_timelapse.show_zoom_controls') === true;
}
public function getMaxZoomLevel() {
return $this->get('zoom_timelapse.max_zoom_level') ?? 4.0;
}
public function isTimelapseReverseEnabled() {
return $this->get('zoom_timelapse.timelapse_reverse_enabled') === true;
}
// SEO Helper
public function getCustomTitle() {
$title = $this->get('seo.custom_title');
return !empty($title) ? $title : null;
}
public function getMetaDescription() {
return $this->get('seo.meta_description') ?? '';
}
public function getMetaKeywords() {
return $this->get('seo.meta_keywords') ?? '';
}
} }
+483 -20
View File
@@ -559,7 +559,7 @@ class VisualCalendarManager {
} }
// === AI-EREIGNISSE === // === AI-EREIGNISSE ===
if (!empty($aiEvents)) { if (!empty($aiEvents) && (!$this->settingsManager || $this->settingsManager->isAIEventsEnabled())) {
$output .= '<div class="ai-events-section">'; $output .= '<div class="ai-events-section">';
$output .= '<h5>🤖 AI-erkannte Ereignisse</h5>'; $output .= '<h5>🤖 AI-erkannte Ereignisse</h5>';
$output .= '<div class="ai-events-grid">'; $output .= '<div class="ai-events-grid">';
@@ -1073,6 +1073,197 @@ class AdminManager {
$output .= '</div>'; $output .= '</div>';
$output .= '</div>'; // settings-group $output .= '</div>'; // settings-group
// UI Display Settings (Punkt 2)
$output .= '<div class="settings-group">';
$output .= '<h4>🖼️ UI Anzeige</h4>';
$output .= '<div class="setting-row">';
$output .= '<span class="setting-label">Empfehlungs-Banner anzeigen</span>';
$output .= '<div class="setting-input">';
$output .= '<label class="toggle-switch">';
$output .= '<input type="checkbox" id="setting-show-banner" ' . ($settingsManager->get('ui_display.show_recommendation_banner') ? 'checked' : '') . '>';
$output .= '<span class="toggle-slider"></span>';
$output .= '</label>';
$output .= '</div>';
$output .= '</div>';
$output .= '<div class="setting-row">';
$output .= '<span class="setting-label">QR-Code Section anzeigen</span>';
$output .= '<div class="setting-input">';
$output .= '<label class="toggle-switch">';
$output .= '<input type="checkbox" id="setting-show-qr" ' . ($settingsManager->get('ui_display.show_qr_code') ? 'checked' : '') . '>';
$output .= '<span class="toggle-slider"></span>';
$output .= '</label>';
$output .= '</div>';
$output .= '</div>';
$output .= '<div class="setting-row">';
$output .= '<span class="setting-label">Social Media Links anzeigen</span>';
$output .= '<div class="setting-input">';
$output .= '<label class="toggle-switch">';
$output .= '<input type="checkbox" id="setting-show-social" ' . ($settingsManager->get('ui_display.show_social_media') ? 'checked' : '') . '>';
$output .= '<span class="toggle-slider"></span>';
$output .= '</label>';
$output .= '</div>';
$output .= '</div>';
$output .= '<div class="setting-row">';
$output .= '<span class="setting-label">Patrouille Suisse Section anzeigen</span>';
$output .= '<div class="setting-input">';
$output .= '<label class="toggle-switch">';
$output .= '<input type="checkbox" id="setting-show-patrouille" ' . ($settingsManager->get('ui_display.show_patrouille_suisse') ? 'checked' : '') . '>';
$output .= '<span class="toggle-slider"></span>';
$output .= '</label>';
$output .= '</div>';
$output .= '</div>';
$output .= '</div>'; // settings-group
// Zoom & Timelapse Settings (Punkt 3)
$output .= '<div class="settings-group">';
$output .= '<h4>🔍 Zoom & Timelapse</h4>';
$output .= '<div class="setting-row">';
$output .= '<span class="setting-label">Zoom-Controls anzeigen</span>';
$output .= '<div class="setting-input">';
$output .= '<label class="toggle-switch">';
$output .= '<input type="checkbox" id="setting-show-zoom" ' . ($settingsManager->get('zoom_timelapse.show_zoom_controls') ? 'checked' : '') . '>';
$output .= '<span class="toggle-slider"></span>';
$output .= '</label>';
$output .= '</div>';
$output .= '</div>';
$output .= '<div class="setting-row">';
$output .= '<span class="setting-label">Max Zoom-Level</span>';
$output .= '<div class="setting-input">';
$output .= '<input type="number" id="setting-max-zoom" class="number-input" min="1.5" max="4.0" step="0.5" value="' . $settingsManager->get('zoom_timelapse.max_zoom_level') . '">';
$output .= '</div>';
$output .= '</div>';
$output .= '<div class="setting-row">';
$output .= '<span class="setting-label">Timelapse Rückwärts-Modus</span>';
$output .= '<div class="setting-input">';
$output .= '<label class="toggle-switch">';
$output .= '<input type="checkbox" id="setting-timelapse-reverse" ' . ($settingsManager->get('zoom_timelapse.timelapse_reverse_enabled') ? 'checked' : '') . '>';
$output .= '<span class="toggle-slider"></span>';
$output .= '</label>';
$output .= '</div>';
$output .= '</div>';
$output .= '</div>'; // settings-group
// Content Management Settings (Punkt 5)
$output .= '<div class="settings-group">';
$output .= '<h4>📝 Content Management</h4>';
$output .= '<div class="setting-row">';
$output .= '<span class="setting-label">Gästebuch aktivieren</span>';
$output .= '<div class="setting-input">';
$output .= '<label class="toggle-switch">';
$output .= '<input type="checkbox" id="setting-guestbook-enabled" ' . ($settingsManager->get('content.guestbook_enabled') ? 'checked' : '') . '>';
$output .= '<span class="toggle-slider"></span>';
$output .= '</label>';
$output .= '</div>';
$output .= '</div>';
$output .= '<div class="setting-row">';
$output .= '<span class="setting-label">Galerie aktivieren</span>';
$output .= '<div class="setting-input">';
$output .= '<label class="toggle-switch">';
$output .= '<input type="checkbox" id="setting-gallery-enabled" ' . ($settingsManager->get('content.gallery_enabled') ? 'checked' : '') . '>';
$output .= '<span class="toggle-slider"></span>';
$output .= '</label>';
$output .= '</div>';
$output .= '</div>';
$output .= '<div class="setting-row">';
$output .= '<span class="setting-label">KI-Events anzeigen</span>';
$output .= '<div class="setting-input">';
$output .= '<label class="toggle-switch">';
$output .= '<input type="checkbox" id="setting-ai-events-enabled" ' . ($settingsManager->get('content.ai_events_enabled') ? 'checked' : '') . '>';
$output .= '<span class="toggle-slider"></span>';
$output .= '</label>';
$output .= '</div>';
$output .= '</div>';
$output .= '<div class="setting-row">';
$output .= '<span class="setting-label">Max Gästebuch-Einträge</span>';
$output .= '<div class="setting-input">';
$output .= '<input type="number" id="setting-max-guestbook" class="number-input" min="10" max="200" step="10" value="' . $settingsManager->get('content.max_guestbook_entries') . '">';
$output .= '</div>';
$output .= '</div>';
$output .= '</div>'; // settings-group
// Technical Settings (Punkt 6)
$output .= '<div class="settings-group">';
$output .= '<h4>⚙️ Technische Einstellungen</h4>';
$output .= '<div class="setting-row">';
$output .= '<span class="setting-label">Viewer Update-Intervall (Sekunden)</span>';
$output .= '<div class="setting-input">';
$output .= '<input type="number" id="setting-viewer-interval" class="number-input" min="1" max="60" value="' . $settingsManager->get('technical.viewer_update_interval') . '">';
$output .= '</div>';
$output .= '</div>';
$output .= '<div class="setting-row">';
$output .= '<span class="setting-label">Session Timeout (Sekunden)</span>';
$output .= '<div class="setting-input">';
$output .= '<input type="number" id="setting-session-timeout" class="number-input" min="10" max="300" value="' . $settingsManager->get('technical.session_timeout') . '">';
$output .= '</div>';
$output .= '</div>';
$output .= '</div>'; // settings-group
// Theme Settings (Punkt 7)
$output .= '<div class="settings-group">';
$output .= '<h4>🎨 Theme & Design</h4>';
$output .= '<div class="setting-row">';
$output .= '<span class="setting-label">Standard-Theme</span>';
$output .= '<div class="setting-input">';
$output .= '<select id="setting-default-theme" class="select-input">';
$currentTheme = $settingsManager->get('theme.default_theme');
$output .= '<option value="theme-legacy" ' . ($currentTheme === 'theme-legacy' ? 'selected' : '') . '>Klassisch</option>';
$output .= '<option value="theme-alpine" ' . ($currentTheme === 'theme-alpine' ? 'selected' : '') . '>Alpin</option>';
$output .= '<option value="theme-neo" ' . ($currentTheme === 'theme-neo' ? 'selected' : '') . '>Modern</option>';
$output .= '</select>';
$output .= '</div>';
$output .= '</div>';
$output .= '<div class="setting-row">';
$output .= '<span class="setting-label">Theme-Switcher anzeigen</span>';
$output .= '<div class="setting-input">';
$output .= '<label class="toggle-switch">';
$output .= '<input type="checkbox" id="setting-show-theme-switcher" ' . ($settingsManager->get('theme.show_theme_switcher') ? 'checked' : '') . '>';
$output .= '<span class="toggle-slider"></span>';
$output .= '</label>';
$output .= '</div>';
$output .= '</div>';
$output .= '</div>'; // settings-group
// SEO Settings (Punkt 8)
$output .= '<div class="settings-group">';
$output .= '<h4>🔍 SEO & Meta</h4>';
$output .= '<div class="setting-row">';
$output .= '<span class="setting-label">Custom Title (leer = Standard)</span>';
$output .= '<div class="setting-input">';
$output .= '<input type="text" id="setting-custom-title" class="text-input" placeholder="' . $siteConfig['siteTitle'] . '" value="' . htmlspecialchars($settingsManager->get('seo.custom_title')) . '">';
$output .= '</div>';
$output .= '</div>';
$output .= '<div class="setting-row">';
$output .= '<span class="setting-label">Meta Description</span>';
$output .= '<div class="setting-input">';
$output .= '<textarea id="setting-meta-description" class="textarea-input" rows="2" placeholder="SEO Beschreibung für Google...">' . htmlspecialchars($settingsManager->get('seo.meta_description')) . '</textarea>';
$output .= '</div>';
$output .= '</div>';
$output .= '<div class="setting-row">';
$output .= '<span class="setting-label">Meta Keywords</span>';
$output .= '<div class="setting-input">';
$output .= '<input type="text" id="setting-meta-keywords" class="text-input" placeholder="webcam, zürich, wetter..." value="' . htmlspecialchars($settingsManager->get('seo.meta_keywords')) . '">';
$output .= '</div>';
$output .= '</div>';
$output .= '</div>'; // settings-group
$output .= '</div>'; // admin-settings-panel $output .= '</div>'; // admin-settings-panel
// Bestehender Admin-Content // Bestehender Admin-Content
@@ -1383,11 +1574,11 @@ $minViewersToShow = $settingsManager->get('viewer_display.min_viewers');
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- SEO-optimierter Title --> <!-- SEO-optimierter Title -->
<title><?php echo $siteConfig['siteTitle']; ?></title> <title><?php echo $settingsManager->getCustomTitle() ?: $siteConfig['siteTitle']; ?></title>
<!-- SEO Meta-Tags --> <!-- SEO Meta-Tags -->
<meta name="description" content="Live Webcam Zürich Oberland mit Blick auf Zürichsee. 24/7 Livestream, Tagesvideos, AI-Wettererkennung. Patrouille Suisse Trainingsflüge jeden Montag live verfolgen. Webcam Dürnten auf 616m."> <meta name="description" content="<?php echo !empty($settingsManager->getMetaDescription()) ? htmlspecialchars($settingsManager->getMetaDescription()) : 'Live Webcam Zürich Oberland mit Blick auf Zürichsee. 24/7 Livestream, Tagesvideos, AI-Wettererkennung. Patrouille Suisse Trainingsflüge jeden Montag live verfolgen. Webcam Dürnten auf 616m.'; ?>">
<meta name="keywords" content="Webcam Zürich, Zürichsee Webcam, Zürich Oberland Webcam, Live Webcam Schweiz, Patrouille Suisse Livestream, Wetter Zürich live, Webcam Dürnten, Rapperswil Webcam, Schweizer Alpen Webcam, Wetter Zürich Oberland, <?php echo $siteConfig['siteName']; ?> Webcam, Timelapse Zürich"> <meta name="keywords" content="<?php echo !empty($settingsManager->getMetaKeywords()) ? htmlspecialchars($settingsManager->getMetaKeywords()) : 'Webcam Zürich, Zürichsee Webcam, Zürich Oberland Webcam, Live Webcam Schweiz, Patrouille Suisse Livestream, Wetter Zürich live, Webcam Dürnten, Rapperswil Webcam, Schweizer Alpen Webcam, Wetter Zürich Oberland, ' . $siteConfig['siteName'] . ' Webcam, Timelapse Zürich'; ?>">
<meta name="author" content="<?php echo $siteConfig['author']; ?>"> <meta name="author" content="<?php echo $siteConfig['author']; ?>">
<meta name="robots" content="index, follow, max-image-preview:large"> <meta name="robots" content="index, follow, max-image-preview:large">
<link rel="canonical" href="<?php echo $siteConfig['domainUrl']; ?>/"> <link rel="canonical" href="<?php echo $siteConfig['domainUrl']; ?>/">
@@ -1896,6 +2087,119 @@ button[type="submit"]:hover { background-color: #45a049; }
.modal-next { right: 0; } .modal-next { right: 0; }
.modal-prev { left: 0; } .modal-prev { left: 0; }
/* Admin Settings Panel */
#admin-settings-panel {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 30px;
border-radius: 12px;
margin-bottom: 30px;
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
}
#admin-settings-panel h3 {
color: white;
margin: 0 0 25px 0;
font-size: 24px;
text-align: center;
}
.settings-group {
background: rgba(255, 255, 255, 0.95);
padding: 20px;
border-radius: 8px;
margin-bottom: 15px;
}
.settings-group h4 {
margin: 0 0 15px 0;
color: #667eea;
font-size: 18px;
border-bottom: 2px solid #667eea;
padding-bottom: 8px;
}
.setting-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #f0f0f0;
}
.setting-row:last-child {
border-bottom: none;
}
.setting-label {
font-weight: 500;
color: #333;
flex: 1;
}
.setting-input {
display: flex;
align-items: center;
min-width: 200px;
}
.number-input, .text-input, .select-input {
width: 100%;
padding: 8px 12px;
border: 2px solid #ddd;
border-radius: 5px;
font-size: 14px;
transition: border-color 0.3s;
}
.number-input:focus, .text-input:focus, .select-input:focus {
outline: none;
border-color: #667eea;
}
.textarea-input {
width: 100%;
padding: 8px 12px;
border: 2px solid #ddd;
border-radius: 5px;
font-size: 14px;
font-family: Arial, sans-serif;
resize: vertical;
transition: border-color 0.3s;
}
.textarea-input:focus {
outline: none;
border-color: #667eea;
}
.toggle-switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: 0.3s;
border-radius: 24px;
}
.toggle-slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: white;
transition: 0.3s;
border-radius: 50%;
}
.toggle-switch input:checked + .toggle-slider {
background: linear-gradient(135deg, #667eea, #764ba2);
}
.toggle-switch input:checked + .toggle-slider:before {
transform: translateX(26px);
}
/* Language Switch */ /* Language Switch */
#language-switch { position: fixed; top: 10px; right: 10px; z-index: 1000; background-color: rgba(255, 255, 255, 0.8); border-radius: 5px; padding: 5px; } #language-switch { position: fixed; top: 10px; right: 10px; z-index: 1000; background-color: rgba(255, 255, 255, 0.8); border-radius: 5px; padding: 5px; }
.lang-button { background: none; border: none; cursor: pointer; padding: 5px; opacity: 0.7; transition: opacity 0.3s; margin: 0 2px; } .lang-button { background: none; border: none; cursor: pointer; padding: 5px; opacity: 0.7; transition: opacity 0.3s; margin: 0 2px; }
@@ -2105,7 +2409,7 @@ body.theme-neo footer {
<script src="https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js"></script>
</head> </head>
<body class="theme-legacy"> <body class="<?php echo $settingsManager->getDefaultTheme(); ?>">
<div class="sun-overlay" aria-hidden="true"></div> <div class="sun-overlay" aria-hidden="true"></div>
@@ -2147,12 +2451,12 @@ body.theme-neo footer {
<?php endif; ?> <?php endif; ?>
</ul> </ul>
</nav> </nav>
<!-- <div class="theme-switcher" aria-label="Design wechseln"> <div class="theme-switcher" aria-label="Design wechseln" style="display: <?php echo $settingsManager->shouldShowThemeSwitcher() ? 'flex' : 'none'; ?>;">
<span data-en="Design" data-de="Design" data-it="Design" data-fr="Design" data-zh="设计">Design</span> <span data-en="Design" data-de="Design" data-it="Design" data-fr="Design" data-zh="设计">Design</span>
<button class="theme-button active" data-theme="theme-legacy" type="button" data-en="Classic" data-de="Klassisch" data-it="Classico" data-fr="Classique" data-zh="经典">Klassisch</button> <button class="theme-button active" data-theme="theme-legacy" type="button" data-en="Classic" data-de="Klassisch" data-it="Classico" data-fr="Classique" data-zh="经典">Klassisch</button>
<button class="theme-button" data-theme="theme-alpine" type="button" data-en="Alpine" data-de="Alpin" data-it="Alpino" data-fr="Alpin" data-zh="高山">Alpin</button> <button class="theme-button" data-theme="theme-alpine" type="button" data-en="Alpine" data-de="Alpin" data-it="Alpino" data-fr="Alpin" data-zh="高山">Alpin</button>
<button class="theme-button" data-theme="theme-neo" type="button" data-en="Modern" data-de="Modern" data-it="Moderno" data-fr="Moderne" data-zh="现代">Modern</button> <button class="theme-button" data-theme="theme-neo" type="button" data-en="Modern" data-de="Modern" data-it="Moderno" data-fr="Moderne" data-zh="现代">Modern</button>
</div> --> </div>
</div> </div>
</header> </header>
@@ -2176,7 +2480,7 @@ body.theme-neo footer {
</div> </div>
</section> </section>
<div class="banner-container"> <div class="banner-container" style="display: <?php echo $settingsManager->shouldShowRecommendationBanner() ? 'block' : 'none'; ?>;">
<div class="recommendation-banner"> <div class="recommendation-banner">
<h2>Unsere Empfehlungen</h2> <h2>Unsere Empfehlungen</h2>
<div class="sponsor-logos"> <div class="sponsor-logos">
@@ -2221,7 +2525,7 @@ body.theme-neo footer {
<!-- <!--
CONTROLS --> CONTROLS -->
<div id="zoom-controls" class="zoom-controls" aria-label="Zoom Steuerung"> <div id="zoom-controls" class="zoom-controls" aria-label="Zoom Steuerung" style="display: <?php echo $settingsManager->shouldShowZoomControls() ? 'flex' : 'none'; ?>;">
<button type="button" onclick="adjustZoom(-0.5)" class="zoom-btn" title="Zoom out"></button> <button type="button" onclick="adjustZoom(-0.5)" class="zoom-btn" title="Zoom out"></button>
<input type="range" id="zoom-range" class="zoom-slider" min="1" max="4" value="1" step="0.5"> <input type="range" id="zoom-range" class="zoom-slider" min="1" max="4" value="1" step="0.5">
<span id="zoom-value" class="zoom-value">1.0x</span> <span id="zoom-value" class="zoom-value">1.0x</span>
@@ -2302,7 +2606,7 @@ body.theme-neo footer {
</section> </section>
<!-- QR CODE --> <!-- QR CODE -->
<section id="qr-code" class="section"> <section id="qr-code" class="section" style="display: <?php echo $settingsManager->shouldShowQRCode() ? 'block' : 'none'; ?>;">
<div class="container" style="text-align: center;"> <div class="container" style="text-align: center;">
<h1> <h1>
<p data-en="Follow us and share with friends" data-de="Folge uns und teile mit Freunden" data-it="Seguici e condividi con gli amici" data-fr="Suivez-nous et partagez avec vos amis" data-zh="关注我们并分享给朋友"> <p data-en="Follow us and share with friends" data-de="Folge uns und teile mit Freunden" data-it="Seguici e condividi con gli amici" data-fr="Suivez-nous et partagez avec vos amis" data-zh="关注我们并分享给朋友">
@@ -2317,7 +2621,7 @@ body.theme-neo footer {
</section> </section>
<!-- GUESTBOOK --> <!-- GUESTBOOK -->
<section id="guestbook" class="section"> <section id="guestbook" class="section" style="display: <?php echo $settingsManager->isGuestbookEnabled() ? 'block' : 'none'; ?>;">
<div class="container"> <div class="container">
<h2 data-en="Guestbook" data-de="Gästebuch" data-it="Libro degli ospiti" data-fr="Livre d'or" data-zh="留言簿">Gästebuch</h2> <h2 data-en="Guestbook" data-de="Gästebuch" data-it="Libro degli ospiti" data-fr="Livre d'or" data-zh="留言簿">Gästebuch</h2>
<?php <?php
@@ -2343,7 +2647,7 @@ body.theme-neo footer {
</section> </section>
<!-- GALLERY --> <!-- GALLERY -->
<section id="gallery" class="section"> <section id="gallery" class="section" style="display: <?php echo $settingsManager->isGalleryEnabled() ? 'block' : 'none'; ?>;">
<div class="container"> <div class="container">
<h2 data-en="Image Gallery" data-de="Bildergalerie" data-it="Galleria immagini" data-fr="Galerie d'images" data-zh="图片库">Bildergalerie</h2> <h2 data-en="Image Gallery" data-de="Bildergalerie" data-it="Galleria immagini" data-fr="Galerie d'images" data-zh="图片库">Bildergalerie</h2>
<div class="gallery-wrapper"> <div class="gallery-wrapper">
@@ -2397,7 +2701,7 @@ body.theme-neo footer {
<?php endif; ?> <?php endif; ?>
<!-- PATROUILLE SUISSE SEKTION --> <!-- PATROUILLE SUISSE SEKTION -->
<section id="patrouille-suisse" class="section" style="background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);"> <section id="patrouille-suisse" class="section" style="background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); display: <?php echo $settingsManager->shouldShowPatrouillesuisse() ? 'block' : 'none'; ?>;">
<div class="container"> <div class="container">
<h2 style="color: #fff; text-align: center;" data-en="Patrouille Suisse Live - Watch Training Flights" data-de="Patrouille Suisse Live - Trainingsflüge Beobachten" data-it="Patrouille Suisse Live - Guarda i voli di addestramento" data-fr="Patrouille Suisse en direct - Regardez les vols d'entraînement" data-zh="瑞士巡逻兵直播 - 观看训练飞行">Patrouille Suisse Live - Trainingsflüge Beobachten</h2> <h2 style="color: #fff; text-align: center;" data-en="Patrouille Suisse Live - Watch Training Flights" data-de="Patrouille Suisse Live - Trainingsflüge Beobachten" data-it="Patrouille Suisse Live - Guarda i voli di addestramento" data-fr="Patrouille Suisse en direct - Regardez les vols d'entraînement" data-zh="瑞士巡逻兵直播 - 观看训练飞行">Patrouille Suisse Live - Trainingsflüge Beobachten</h2>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 30px; margin-top: 30px;"> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 30px; margin-top: 30px;">
@@ -2525,20 +2829,20 @@ body.theme-neo footer {
<footer> <footer>
<div class="container"> <div class="container">
<!-- Social Media Icons --> <!-- Social Media Icons -->
<div class="footer-social" style="text-align: center; margin-bottom: 20px;"> <div class="footer-social social-media-container" style="text-align: center; margin-bottom: 20px; display: <?php echo $settingsManager->shouldShowSocialMedia() ? 'block' : 'none'; ?>;">
<a href="https://www.instagram.com/auroraweatherlivecam" target="_blank" rel="noopener noreferrer" title="Folge uns auf Instagram" style="display: inline-block; margin: 0 10px; color: #E1306C; font-size: 24px;"> <a href="https://www.instagram.com/auroraweatherlivecam" target="_blank" class="social-link" rel="noopener noreferrer" title="Folge uns auf Instagram" style="display: inline-block; margin: 0 10px; color: #E1306C; font-size: 24px;">
<i class="fab fa-instagram" aria-hidden="true"></i> <i class="fab fa-instagram" aria-hidden="true"></i>
<span class="sr-only">Instagram</span> <span class="sr-only">Instagram</span>
</a> </a>
<a href="https://www.facebook.com/auroraweatherlivecam" target="_blank" rel="noopener noreferrer" title="Folge uns auf Facebook" style="display: inline-block; margin: 0 10px; color: #1877F2; font-size: 24px;"> <a href="https://www.facebook.com/auroraweatherlivecam" target="_blank" class="social-link" rel="noopener noreferrer" title="Folge uns auf Facebook" style="display: inline-block; margin: 0 10px; color: #1877F2; font-size: 24px;">
<i class="fab fa-facebook" aria-hidden="true"></i> <i class="fab fa-facebook" aria-hidden="true"></i>
<span class="sr-only">Facebook</span> <span class="sr-only">Facebook</span>
</a> </a>
<a href="https://www.youtube.com/@auroraweatherlivecam" target="_blank" rel="noopener noreferrer" title="Abonniere unseren YouTube Kanal" style="display: inline-block; margin: 0 10px; color: #FF0000; font-size: 24px;"> <a href="https://www.youtube.com/@auroraweatherlivecam" target="_blank" class="social-link" rel="noopener noreferrer" title="Abonniere unseren YouTube Kanal" style="display: inline-block; margin: 0 10px; color: #FF0000; font-size: 24px;">
<i class="fab fa-youtube" aria-hidden="true"></i> <i class="fab fa-youtube" aria-hidden="true"></i>
<span class="sr-only">YouTube</span> <span class="sr-only">YouTube</span>
</a> </a>
<a href="https://www.tiktok.com/@auroraweatherlivecam" target="_blank" rel="noopener noreferrer" title="Folge uns auf TikTok" style="display: inline-block; margin: 0 10px; color: #000000; font-size: 24px;"> <a href="https://www.tiktok.com/@auroraweatherlivecam" target="_blank" class="social-link" rel="noopener noreferrer" title="Folge uns auf TikTok" style="display: inline-block; margin: 0 10px; color: #000000; font-size: 24px;">
<i class="fab fa-tiktok" aria-hidden="true"></i> <i class="fab fa-tiktok" aria-hidden="true"></i>
<span class="sr-only">TikTok</span> <span class="sr-only">TikTok</span>
</a> </a>
@@ -2617,6 +2921,7 @@ const TimelapseController = {
availableSpeeds: [1, 10, 100], availableSpeeds: [1, 10, 100],
intervalId: null, intervalId: null,
baseInterval: 200, baseInterval: 200,
reverseEnabled: <?php echo $settingsManager->isTimelapseReverseEnabled() ? 'true' : 'false'; ?>,
init: function() { init: function() {
this.setupControls(); this.setupControls();
@@ -2632,7 +2937,7 @@ const TimelapseController = {
<button id="tl-play-pause" class="tl-btn" title="Play/Pause"> <button id="tl-play-pause" class="tl-btn" title="Play/Pause">
<i class="fas fa-play"></i> <i class="fas fa-play"></i>
</button> </button>
<button id="tl-reverse" class="tl-btn" title="Rückwärts"> <button id="tl-reverse" class="tl-btn" title="Rückwärts" style="display: ${this.reverseEnabled ? 'inline-block' : 'none'};">
<i class="fas fa-backward"></i> <i class="fas fa-backward"></i>
</button> </button>
<div class="tl-slider-container"> <div class="tl-slider-container">
@@ -2867,16 +3172,96 @@ const AdminSettings = {
}, },
applySettingImmediately: function(key, value) { applySettingImmediately: function(key, value) {
const boolValue = (value === true || value === 'true');
switch(key) { switch(key) {
case 'viewer_display.enabled': case 'viewer_display.enabled':
const viewerEl = document.getElementById('viewer-stat-container'); const viewerEl = document.getElementById('viewer-stat-container');
if (viewerEl) { if (viewerEl) {
viewerEl.style.display = (value === true || value === 'true') ? 'inline-flex' : 'none'; viewerEl.style.display = boolValue ? 'inline-flex' : 'none';
} }
break; break;
case 'viewer_display.min_viewers': case 'viewer_display.min_viewers':
window.minViewersToShow = parseInt(value); window.minViewersToShow = parseInt(value);
break; break;
// UI Display (Punkt 2)
case 'ui_display.show_recommendation_banner':
const bannerEl = document.querySelector('.banner-container');
if (bannerEl) bannerEl.style.display = boolValue ? 'block' : 'none';
break;
case 'ui_display.show_qr_code':
const qrSection = document.getElementById('qr-code');
if (qrSection) qrSection.style.display = boolValue ? 'block' : 'none';
break;
case 'ui_display.show_social_media':
const socialEls = document.querySelectorAll('.social-link, .social-media-container');
socialEls.forEach(el => el.style.display = boolValue ? '' : 'none');
break;
case 'ui_display.show_patrouille_suisse':
const patrouilleSection = document.getElementById('patrouille-suisse');
if (patrouilleSection) patrouilleSection.style.display = boolValue ? 'block' : 'none';
break;
// Zoom & Timelapse (Punkt 3)
case 'zoom_timelapse.show_zoom_controls':
const zoomControls = document.getElementById('zoom-controls');
if (zoomControls) zoomControls.style.display = boolValue ? 'flex' : 'none';
break;
case 'zoom_timelapse.max_zoom_level':
window.maxZoomLevel = parseFloat(value);
this.showNotification('Max Zoom: ' + value + 'x (Reload empfohlen)', 'success');
break;
case 'zoom_timelapse.timelapse_reverse_enabled':
const reverseBtn = document.getElementById('tl-reverse');
if (reverseBtn) reverseBtn.style.display = boolValue ? 'inline-block' : 'none';
break;
// Content Management (Punkt 5)
case 'content.guestbook_enabled':
const guestbookSection = document.getElementById('guestbook');
if (guestbookSection) guestbookSection.style.display = boolValue ? 'block' : 'none';
break;
case 'content.gallery_enabled':
const gallerySection = document.getElementById('gallery');
if (gallerySection) gallerySection.style.display = boolValue ? 'block' : 'none';
break;
case 'content.ai_events_enabled':
const aiSections = document.querySelectorAll('.ai-events-section');
aiSections.forEach(section => section.style.display = boolValue ? 'block' : 'none');
this.showNotification('AI-Events ' + (boolValue ? 'aktiviert' : 'deaktiviert') + ' (Reload empfohlen)', 'success');
break;
case 'content.max_guestbook_entries':
this.showNotification('Max Einträge: ' + value + ' (Reload empfohlen)', 'success');
break;
// Technical (Punkt 6)
case 'technical.viewer_update_interval':
this.showNotification('Update-Intervall: ' + value + 's (Reload empfohlen)', 'success');
break;
case 'technical.session_timeout':
this.showNotification('Session Timeout: ' + value + 's (Reload empfohlen)', 'success');
break;
// Theme (Punkt 7)
case 'theme.default_theme':
document.body.className = value;
this.showNotification('Theme geändert: ' + value, 'success');
break;
case 'theme.show_theme_switcher':
const themeSwitcher = document.querySelector('.theme-switcher');
if (themeSwitcher) themeSwitcher.style.display = boolValue ? 'flex' : 'none';
break;
// SEO (Punkt 8)
case 'seo.custom_title':
document.title = value || document.title;
this.showNotification('Title aktualisiert (Meta bei Reload)', 'success');
break;
case 'seo.meta_description':
case 'seo.meta_keywords':
this.showNotification('SEO Meta gespeichert (wirksam bei Reload)', 'success');
break;
} }
}, },
@@ -2896,6 +3281,84 @@ const AdminSettings = {
document.getElementById('setting-allow-download')?.addEventListener('change', (e) => { document.getElementById('setting-allow-download')?.addEventListener('change', (e) => {
this.updateSetting('video_mode.allow_download', e.target.checked); this.updateSetting('video_mode.allow_download', e.target.checked);
}); });
// UI Display Settings (Punkt 2)
document.getElementById('setting-show-banner')?.addEventListener('change', (e) => {
this.updateSetting('ui_display.show_recommendation_banner', e.target.checked);
});
document.getElementById('setting-show-qr')?.addEventListener('change', (e) => {
this.updateSetting('ui_display.show_qr_code', e.target.checked);
});
document.getElementById('setting-show-social')?.addEventListener('change', (e) => {
this.updateSetting('ui_display.show_social_media', e.target.checked);
});
document.getElementById('setting-show-patrouille')?.addEventListener('change', (e) => {
this.updateSetting('ui_display.show_patrouille_suisse', e.target.checked);
});
// Zoom & Timelapse Settings (Punkt 3)
document.getElementById('setting-show-zoom')?.addEventListener('change', (e) => {
this.updateSetting('zoom_timelapse.show_zoom_controls', e.target.checked);
});
document.getElementById('setting-max-zoom')?.addEventListener('change', (e) => {
this.updateSetting('zoom_timelapse.max_zoom_level', parseFloat(e.target.value));
});
document.getElementById('setting-timelapse-reverse')?.addEventListener('change', (e) => {
this.updateSetting('zoom_timelapse.timelapse_reverse_enabled', e.target.checked);
});
// Content Management Settings (Punkt 5)
document.getElementById('setting-guestbook-enabled')?.addEventListener('change', (e) => {
this.updateSetting('content.guestbook_enabled', e.target.checked);
});
document.getElementById('setting-gallery-enabled')?.addEventListener('change', (e) => {
this.updateSetting('content.gallery_enabled', e.target.checked);
});
document.getElementById('setting-ai-events-enabled')?.addEventListener('change', (e) => {
this.updateSetting('content.ai_events_enabled', e.target.checked);
});
document.getElementById('setting-max-guestbook')?.addEventListener('change', (e) => {
this.updateSetting('content.max_guestbook_entries', parseInt(e.target.value));
});
// Technical Settings (Punkt 6)
document.getElementById('setting-viewer-interval')?.addEventListener('change', (e) => {
this.updateSetting('technical.viewer_update_interval', parseInt(e.target.value));
});
document.getElementById('setting-session-timeout')?.addEventListener('change', (e) => {
this.updateSetting('technical.session_timeout', parseInt(e.target.value));
});
// Theme Settings (Punkt 7)
document.getElementById('setting-default-theme')?.addEventListener('change', (e) => {
this.updateSetting('theme.default_theme', e.target.value);
});
document.getElementById('setting-show-theme-switcher')?.addEventListener('change', (e) => {
this.updateSetting('theme.show_theme_switcher', e.target.checked);
});
// SEO Settings (Punkt 8)
document.getElementById('setting-custom-title')?.addEventListener('change', (e) => {
this.updateSetting('seo.custom_title', e.target.value);
});
document.getElementById('setting-meta-description')?.addEventListener('change', (e) => {
this.updateSetting('seo.meta_description', e.target.value);
});
document.getElementById('setting-meta-keywords')?.addEventListener('change', (e) => {
this.updateSetting('seo.meta_keywords', e.target.value);
});
}, },
showNotification: function(message, type) { showNotification: function(message, type) {