Merge pull request #40 from metacube2/claude/mail-finetuning-webapp-01BsRXQNeVFrCBky8aw35YHw
asdf
This commit is contained in:
@@ -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,52 @@ 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' => ''
|
||||||
|
],
|
||||||
|
// Weather Widget
|
||||||
|
'weather' => [
|
||||||
|
'enabled' => true,
|
||||||
|
'api_key' => '',
|
||||||
|
'location' => 'Oberdürnten,CH',
|
||||||
|
'lat' => '47.2833',
|
||||||
|
'lon' => '8.7167',
|
||||||
|
'update_interval' => 5, // Minuten
|
||||||
|
'units' => 'metric' // metric (Celsius) oder imperial (Fahrenheit)
|
||||||
|
],
|
||||||
'last_updated' => null,
|
'last_updated' => null,
|
||||||
'updated_by' => null
|
'updated_by' => null
|
||||||
];
|
];
|
||||||
@@ -123,4 +170,111 @@ 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') ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Weather Helper
|
||||||
|
public function isWeatherEnabled() {
|
||||||
|
return $this->get('weather.enabled') === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWeatherApiKey() {
|
||||||
|
return $this->get('weather.api_key') ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWeatherLocation() {
|
||||||
|
return $this->get('weather.location') ?? 'Oberdürnten,CH';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWeatherCoords() {
|
||||||
|
return [
|
||||||
|
'lat' => $this->get('weather.lat') ?? '47.2833',
|
||||||
|
'lon' => $this->get('weather.lon') ?? '8.7167'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWeatherUpdateInterval() {
|
||||||
|
return $this->get('weather.update_interval') ?? 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWeatherUnits() {
|
||||||
|
return $this->get('weather.units') ?? 'metric';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,215 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* WeatherManager - Holt und cached Wetterdaten von Open-Meteo (kostenlos!)
|
||||||
|
* Keine API Key nötig!
|
||||||
|
*/
|
||||||
|
class WeatherManager {
|
||||||
|
private $settingsManager;
|
||||||
|
private $cacheFile = 'weather_cache.json';
|
||||||
|
private $cacheTime = 300; // 5 Minuten in Sekunden
|
||||||
|
|
||||||
|
public function __construct($settingsManager) {
|
||||||
|
$this->settingsManager = $settingsManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holt aktuelle Wetterdaten (cached)
|
||||||
|
*/
|
||||||
|
public function getCurrentWeather() {
|
||||||
|
// Prüfe ob Weather aktiviert ist
|
||||||
|
if (!$this->settingsManager->isWeatherEnabled()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prüfe Cache
|
||||||
|
$cached = $this->getCache();
|
||||||
|
if ($cached !== null) {
|
||||||
|
return $cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hole frische Daten von API (Open-Meteo)
|
||||||
|
$coords = $this->settingsManager->getWeatherCoords();
|
||||||
|
|
||||||
|
// Open-Meteo API URL - komplett kostenlos, kein API Key!
|
||||||
|
$url = "https://api.open-meteo.com/v1/forecast?" . http_build_query([
|
||||||
|
'latitude' => $coords['lat'],
|
||||||
|
'longitude' => $coords['lon'],
|
||||||
|
'current' => 'temperature_2m,relative_humidity_2m,precipitation,weather_code,wind_speed_10m,wind_direction_10m,pressure_msl,cloud_cover',
|
||||||
|
'timezone' => 'Europe/Zurich'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 5);
|
||||||
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($httpCode !== 200 || !$response) {
|
||||||
|
return ['error' => 'API Fehler'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($response, true);
|
||||||
|
if (!$data || !isset($data['current'])) {
|
||||||
|
return ['error' => 'Ungültige API Antwort'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$current = $data['current'];
|
||||||
|
|
||||||
|
// Formatiere Daten
|
||||||
|
$weather = [
|
||||||
|
'temp' => round($current['temperature_2m'], 1),
|
||||||
|
'feels_like' => round($current['temperature_2m'], 1), // Open-Meteo hat keine "feels like"
|
||||||
|
'humidity' => $current['relative_humidity_2m'],
|
||||||
|
'pressure' => round($current['pressure_msl'], 0),
|
||||||
|
'wind_speed' => round($current['wind_speed_10m'], 1), // Schon in km/h!
|
||||||
|
'wind_deg' => $current['wind_direction_10m'],
|
||||||
|
'wind_direction' => $this->getWindDirection($current['wind_direction_10m']),
|
||||||
|
'clouds' => $current['cloud_cover'] ?? 0,
|
||||||
|
'description' => $this->getWeatherDescription($current['weather_code']),
|
||||||
|
'icon' => $this->getWeatherIcon($current['weather_code']),
|
||||||
|
'rain_1h' => $current['precipitation'] ?? 0,
|
||||||
|
'snow_1h' => 0, // Open-Meteo gibt Niederschlag gesamt
|
||||||
|
'location' => $this->settingsManager->getWeatherLocation(),
|
||||||
|
'timestamp' => time()
|
||||||
|
];
|
||||||
|
|
||||||
|
// Cache speichern
|
||||||
|
$this->saveCache($weather);
|
||||||
|
|
||||||
|
return $weather;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wandelt WMO Weather Code in Beschreibung um
|
||||||
|
* https://open-meteo.com/en/docs
|
||||||
|
*/
|
||||||
|
private function getWeatherDescription($code) {
|
||||||
|
$descriptions = [
|
||||||
|
0 => 'Klar',
|
||||||
|
1 => 'Überwiegend klar',
|
||||||
|
2 => 'Teilweise bewölkt',
|
||||||
|
3 => 'Bewölkt',
|
||||||
|
45 => 'Neblig',
|
||||||
|
48 => 'Nebel mit Reifablagerung',
|
||||||
|
51 => 'Leichter Nieselregen',
|
||||||
|
53 => 'Mäßiger Nieselregen',
|
||||||
|
55 => 'Dichter Nieselregen',
|
||||||
|
61 => 'Leichter Regen',
|
||||||
|
63 => 'Mäßiger Regen',
|
||||||
|
65 => 'Starker Regen',
|
||||||
|
71 => 'Leichter Schneefall',
|
||||||
|
73 => 'Mäßiger Schneefall',
|
||||||
|
75 => 'Starker Schneefall',
|
||||||
|
77 => 'Schneegraupeln',
|
||||||
|
80 => 'Leichte Regenschauer',
|
||||||
|
81 => 'Mäßige Regenschauer',
|
||||||
|
82 => 'Starke Regenschauer',
|
||||||
|
85 => 'Leichte Schneeschauer',
|
||||||
|
86 => 'Starke Schneeschauer',
|
||||||
|
95 => 'Gewitter',
|
||||||
|
96 => 'Gewitter mit leichtem Hagel',
|
||||||
|
99 => 'Gewitter mit starkem Hagel'
|
||||||
|
];
|
||||||
|
|
||||||
|
return $descriptions[$code] ?? 'Unbekannt';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wandelt WMO Weather Code in Icon-Code um (OpenWeatherMap kompatibel)
|
||||||
|
*/
|
||||||
|
private function getWeatherIcon($code) {
|
||||||
|
if ($code == 0) return '01d'; // Klar
|
||||||
|
if ($code >= 1 && $code <= 2) return '02d'; // Teilweise bewölkt
|
||||||
|
if ($code == 3) return '04d'; // Bewölkt
|
||||||
|
if ($code >= 45 && $code <= 48) return '50d'; // Nebel
|
||||||
|
if ($code >= 51 && $code <= 55) return '09d'; // Nieselregen
|
||||||
|
if ($code >= 61 && $code <= 65) return '10d'; // Regen
|
||||||
|
if ($code >= 71 && $code <= 77) return '13d'; // Schnee
|
||||||
|
if ($code >= 80 && $code <= 82) return '09d'; // Regenschauer
|
||||||
|
if ($code >= 85 && $code <= 86) return '13d'; // Schneeschauer
|
||||||
|
if ($code >= 95 && $code <= 99) return '11d'; // Gewitter
|
||||||
|
|
||||||
|
return '01d'; // Default
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wandelt Windrichtung (Grad) in Kompassrichtung um
|
||||||
|
*/
|
||||||
|
private function getWindDirection($deg) {
|
||||||
|
$directions = ['N', 'NNO', 'NO', 'ONO', 'O', 'OSO', 'SO', 'SSO', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'];
|
||||||
|
$index = round($deg / 22.5) % 16;
|
||||||
|
return $directions[$index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holt Daten aus Cache (wenn noch gültig)
|
||||||
|
*/
|
||||||
|
private function getCache() {
|
||||||
|
if (!file_exists($this->cacheFile)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = file_get_contents($this->cacheFile);
|
||||||
|
$data = json_decode($content, true);
|
||||||
|
|
||||||
|
if (!$data || !isset($data['timestamp'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update-Intervall aus Settings holen (in Minuten)
|
||||||
|
$updateInterval = $this->settingsManager->getWeatherUpdateInterval() * 60; // Minuten -> Sekunden
|
||||||
|
|
||||||
|
// Prüfe ob Cache noch gültig
|
||||||
|
if (time() - $data['timestamp'] < $updateInterval) {
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Speichert Daten im Cache
|
||||||
|
*/
|
||||||
|
private function saveCache($data) {
|
||||||
|
$json = json_encode($data, JSON_PRETTY_PRINT);
|
||||||
|
file_put_contents($this->cacheFile, $json, LOCK_EX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt Wetter-Icon-Emoji zurück
|
||||||
|
*/
|
||||||
|
public function getWeatherEmoji($iconCode) {
|
||||||
|
$map = [
|
||||||
|
'01d' => '☀️', '01n' => '🌙',
|
||||||
|
'02d' => '⛅', '02n' => '☁️',
|
||||||
|
'03d' => '☁️', '03n' => '☁️',
|
||||||
|
'04d' => '☁️', '04n' => '☁️',
|
||||||
|
'09d' => '🌧️', '09n' => '🌧️',
|
||||||
|
'10d' => '🌦️', '10n' => '🌧️',
|
||||||
|
'11d' => '⛈️', '11n' => '⛈️',
|
||||||
|
'13d' => '❄️', '13n' => '❄️',
|
||||||
|
'50d' => '🌫️', '50n' => '🌫️'
|
||||||
|
];
|
||||||
|
return $map[$iconCode] ?? '🌤️';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AJAX Handler für Wetter-Updates
|
||||||
|
*/
|
||||||
|
public function handleAjax() {
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'GET') return;
|
||||||
|
if (!isset($_GET['weather_action'])) return;
|
||||||
|
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
if ($_GET['weather_action'] === 'get') {
|
||||||
|
$weather = $this->getCurrentWeather();
|
||||||
|
echo json_encode(['success' => true, 'data' => $weather]);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
// Clear PHP OPcache
|
||||||
|
if (function_exists('opcache_reset')) {
|
||||||
|
opcache_reset();
|
||||||
|
echo "OPcache cleared successfully!\n";
|
||||||
|
} else {
|
||||||
|
echo "OPcache not available\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear realpath cache
|
||||||
|
clearstatcache(true);
|
||||||
|
echo "Realpath cache cleared!\n";
|
||||||
|
|
||||||
|
echo "\nNow reload the page with CTRL+F5 (hard refresh)\n";
|
||||||
|
?>
|
||||||
+813
-22
@@ -4,12 +4,17 @@ use PHPMailer\PHPMailer\Exception;
|
|||||||
|
|
||||||
require __DIR__ . '/vendor/autoload.php';
|
require __DIR__ . '/vendor/autoload.php';
|
||||||
require_once 'SettingsManager.php';
|
require_once 'SettingsManager.php';
|
||||||
|
require_once 'WeatherManager.php';
|
||||||
|
|
||||||
// SettingsManager initialisieren
|
// SettingsManager initialisieren
|
||||||
$settingsManager = new SettingsManager();
|
$settingsManager = new SettingsManager();
|
||||||
|
|
||||||
// AJAX-Handler für Settings (VOR anderen Ausgaben!)
|
// WeatherManager initialisieren
|
||||||
|
$weatherManager = new WeatherManager($settingsManager);
|
||||||
|
|
||||||
|
// AJAX-Handler für Settings und Weather (VOR anderen Ausgaben!)
|
||||||
$settingsManager->handleAjax();
|
$settingsManager->handleAjax();
|
||||||
|
$weatherManager->handleAjax();
|
||||||
|
|
||||||
if (isset($_GET['download_video'])) {
|
if (isset($_GET['download_video'])) {
|
||||||
$videoDir = './videos/';
|
$videoDir = './videos/';
|
||||||
@@ -559,7 +564,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">';
|
||||||
@@ -1018,7 +1023,7 @@ class AdminManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function displayAdminContent() {
|
public function displayAdminContent() {
|
||||||
global $settingsManager;
|
global $settingsManager, $siteConfig;
|
||||||
|
|
||||||
$feedbacks = json_decode(file_get_contents('feedbacks.json') ?: '[]', true);
|
$feedbacks = json_decode(file_get_contents('feedbacks.json') ?: '[]', true);
|
||||||
|
|
||||||
@@ -1073,6 +1078,251 @@ 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
|
||||||
|
|
||||||
|
// Weather Settings
|
||||||
|
$output .= '<div class="settings-group">';
|
||||||
|
$output .= '<h4>🌤️ Wetter-Widget <span style="font-size:12px; color:#4CAF50;">(Open-Meteo - 100% kostenlos!)</span></h4>';
|
||||||
|
|
||||||
|
$output .= '<div class="setting-row">';
|
||||||
|
$output .= '<span class="setting-label">Wetter-Widget anzeigen</span>';
|
||||||
|
$output .= '<div class="setting-input">';
|
||||||
|
$output .= '<label class="toggle-switch">';
|
||||||
|
$output .= '<input type="checkbox" id="setting-weather-enabled" ' . ($settingsManager->get('weather.enabled') ? 'checked' : '') . '>';
|
||||||
|
$output .= '<span class="toggle-slider"></span>';
|
||||||
|
$output .= '</label>';
|
||||||
|
$output .= '</div>';
|
||||||
|
$output .= '</div>';
|
||||||
|
|
||||||
|
$output .= '<div class="setting-row">';
|
||||||
|
$output .= '<span class="setting-label">Standort (Stadt,Land)</span>';
|
||||||
|
$output .= '<div class="setting-input">';
|
||||||
|
$output .= '<input type="text" id="setting-weather-location" class="text-input" placeholder="Oberdürnten,CH" value="' . htmlspecialchars($settingsManager->get('weather.location')) . '">';
|
||||||
|
$output .= '</div>';
|
||||||
|
$output .= '</div>';
|
||||||
|
|
||||||
|
$output .= '<div class="setting-row">';
|
||||||
|
$output .= '<span class="setting-label">Latitude (Breitengrad)</span>';
|
||||||
|
$output .= '<div class="setting-input">';
|
||||||
|
$output .= '<input type="text" id="setting-weather-lat" class="text-input" placeholder="47.2833" value="' . htmlspecialchars($settingsManager->get('weather.lat')) . '">';
|
||||||
|
$output .= '</div>';
|
||||||
|
$output .= '</div>';
|
||||||
|
|
||||||
|
$output .= '<div class="setting-row">';
|
||||||
|
$output .= '<span class="setting-label">Longitude (Längengrad)</span>';
|
||||||
|
$output .= '<div class="setting-input">';
|
||||||
|
$output .= '<input type="text" id="setting-weather-lon" class="text-input" placeholder="8.7167" value="' . htmlspecialchars($settingsManager->get('weather.lon')) . '">';
|
||||||
|
$output .= '</div>';
|
||||||
|
$output .= '</div>';
|
||||||
|
|
||||||
|
$output .= '<div class="setting-row">';
|
||||||
|
$output .= '<span class="setting-label">Update-Intervall (Minuten)</span>';
|
||||||
|
$output .= '<div class="setting-input">';
|
||||||
|
$output .= '<input type="number" id="setting-weather-interval" class="number-input" min="5" max="60" value="' . $settingsManager->get('weather.update_interval') . '">';
|
||||||
|
$output .= '</div>';
|
||||||
|
$output .= '</div>';
|
||||||
|
|
||||||
|
$output .= '<div class="setting-row">';
|
||||||
|
$output .= '<span class="setting-label">Einheit</span>';
|
||||||
|
$output .= '<div class="setting-input">';
|
||||||
|
$output .= '<select id="setting-weather-units" class="select-input">';
|
||||||
|
$currentUnits = $settingsManager->get('weather.units');
|
||||||
|
$output .= '<option value="metric" ' . ($currentUnits === 'metric' ? 'selected' : '') . '>Metrisch (°C, km/h)</option>';
|
||||||
|
$output .= '<option value="imperial" ' . ($currentUnits === 'imperial' ? 'selected' : '') . '>Imperial (°F, mph)</option>';
|
||||||
|
$output .= '</select>';
|
||||||
|
$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 +1633,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']; ?>/">
|
||||||
@@ -1874,6 +2124,90 @@ button[type="submit"]:hover { background-color: #45a049; }
|
|||||||
|
|
||||||
.delete-btn { background-color: #ff4136; color: white; border: none; padding: 5px 10px; cursor: pointer; font-size: 0.8em; margin-left: 10px; border-radius: 3px; }
|
.delete-btn { background-color: #ff4136; color: white; border: none; padding: 5px 10px; cursor: pointer; font-size: 0.8em; margin-left: 10px; border-radius: 3px; }
|
||||||
|
|
||||||
|
/* Weather Widget */
|
||||||
|
.weather-widget {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 12px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
|
||||||
|
animation: weatherFadeIn 0.5s ease;
|
||||||
|
}
|
||||||
|
.weather-widget.weather-error {
|
||||||
|
background: linear-gradient(135deg, #f44336 0%, #e91e63 100%);
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.weather-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
padding: 10px 15px;
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
border-radius: 8px;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
min-width: 120px;
|
||||||
|
transition: transform 0.3s ease, background 0.3s ease;
|
||||||
|
}
|
||||||
|
.weather-item:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
background: rgba(255, 255, 255, 0.25);
|
||||||
|
}
|
||||||
|
.weather-icon {
|
||||||
|
font-size: 32px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.weather-value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: white;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.weather-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
.weather-description {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 180px;
|
||||||
|
}
|
||||||
|
.weather-description .weather-value {
|
||||||
|
font-size: 16px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
@keyframes weatherFadeIn {
|
||||||
|
from { opacity: 0; transform: translateY(-10px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.weather-widget {
|
||||||
|
gap: 10px;
|
||||||
|
padding: 15px 10px;
|
||||||
|
}
|
||||||
|
.weather-item {
|
||||||
|
min-width: 90px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
}
|
||||||
|
.weather-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
.weather-value {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.weather-label {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Guestbook */
|
/* Guestbook */
|
||||||
.guestbook-entry { background-color: #f9f9f9; border-left: 5px solid #4CAF50; margin-bottom: 20px; padding: 15px; border-radius: 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
|
.guestbook-entry { background-color: #f9f9f9; border-left: 5px solid #4CAF50; margin-bottom: 20px; padding: 15px; border-radius: 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); }
|
||||||
|
|
||||||
@@ -1896,6 +2230,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 +2552,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 +2594,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 +2623,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">
|
||||||
@@ -2194,6 +2641,57 @@ body.theme-neo footer {
|
|||||||
<!-- WEBCAM SECTION -->
|
<!-- WEBCAM SECTION -->
|
||||||
<section id="webcams" class="section">
|
<section id="webcams" class="section">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<!-- WEATHER WIDGET -->
|
||||||
|
<?php if ($settingsManager->isWeatherEnabled()): ?>
|
||||||
|
<?php
|
||||||
|
try {
|
||||||
|
$weather = $weatherManager->getCurrentWeather();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$weather = ['error' => 'Fehler: ' . $e->getMessage()];
|
||||||
|
}
|
||||||
|
if ($weather && !isset($weather['error'])):
|
||||||
|
?>
|
||||||
|
<div id="weather-widget" class="weather-widget">
|
||||||
|
<div class="weather-item weather-temp">
|
||||||
|
<span class="weather-icon">🌡️</span>
|
||||||
|
<span class="weather-value"><?php echo $weather['temp']; ?>°C</span>
|
||||||
|
<span class="weather-label">Temperatur</span>
|
||||||
|
</div>
|
||||||
|
<div class="weather-item weather-wind">
|
||||||
|
<span class="weather-icon">💨</span>
|
||||||
|
<span class="weather-value"><?php echo $weather['wind_speed']; ?> km/h <?php echo $weather['wind_direction']; ?></span>
|
||||||
|
<span class="weather-label">Wind</span>
|
||||||
|
</div>
|
||||||
|
<div class="weather-item weather-pressure">
|
||||||
|
<span class="weather-icon">🔽</span>
|
||||||
|
<span class="weather-value"><?php echo $weather['pressure']; ?> hPa</span>
|
||||||
|
<span class="weather-label">Luftdruck</span>
|
||||||
|
</div>
|
||||||
|
<div class="weather-item weather-humidity">
|
||||||
|
<span class="weather-icon">💧</span>
|
||||||
|
<span class="weather-value"><?php echo $weather['humidity']; ?>%</span>
|
||||||
|
<span class="weather-label">Luftfeuchtigkeit</span>
|
||||||
|
</div>
|
||||||
|
<div class="weather-item weather-description">
|
||||||
|
<span class="weather-icon"><?php echo $weatherManager->getWeatherEmoji($weather['icon']); ?></span>
|
||||||
|
<span class="weather-value"><?php echo $weather['description']; ?></span>
|
||||||
|
<span class="weather-label"><?php echo $weather['location']; ?></span>
|
||||||
|
</div>
|
||||||
|
<?php if ($weather['rain_1h'] > 0 || $weather['snow_1h'] > 0): ?>
|
||||||
|
<div class="weather-item weather-precipitation">
|
||||||
|
<span class="weather-icon"><?php echo $weather['rain_1h'] > 0 ? '🌧️' : '❄️'; ?></span>
|
||||||
|
<span class="weather-value"><?php echo $weather['rain_1h'] > 0 ? $weather['rain_1h'] : $weather['snow_1h']; ?> mm</span>
|
||||||
|
<span class="weather-label">Niederschlag</span>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
<?php elseif ($weather && isset($weather['error'])): ?>
|
||||||
|
<div class="weather-widget weather-error">
|
||||||
|
<span>⚠️ Wetterdaten nicht verfügbar: <?php echo $weather['error']; ?></span>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- VIDEO PLAYER -->
|
<!-- VIDEO PLAYER -->
|
||||||
<div class="video-container" style="border-radius: 12px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.15);">
|
<div class="video-container" style="border-radius: 12px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.15);">
|
||||||
<?php echo $webcamManager->displayWebcam(); ?>
|
<?php echo $webcamManager->displayWebcam(); ?>
|
||||||
@@ -2221,7 +2719,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 +2800,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 +2815,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 +2841,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 +2895,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 +3023,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 +3115,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 +3131,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 +3366,115 @@ 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;
|
||||||
|
|
||||||
|
// Weather Settings
|
||||||
|
case 'weather.enabled':
|
||||||
|
const weatherWidget = document.getElementById('weather-widget');
|
||||||
|
if (weatherWidget) {
|
||||||
|
weatherWidget.style.display = boolValue ? 'flex' : 'none';
|
||||||
|
}
|
||||||
|
this.showNotification('Wetter-Widget ' + (boolValue ? 'aktiviert' : 'deaktiviert'), 'success');
|
||||||
|
break;
|
||||||
|
case 'weather.api_key':
|
||||||
|
case 'weather.location':
|
||||||
|
case 'weather.lat':
|
||||||
|
case 'weather.lon':
|
||||||
|
case 'weather.units':
|
||||||
|
this.showNotification('Wetter-Einstellung gespeichert (Reload empfohlen)', 'success');
|
||||||
|
break;
|
||||||
|
case 'weather.update_interval':
|
||||||
|
this.showNotification('Update-Intervall: ' + value + ' Minuten (Reload empfohlen)', 'success');
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -2896,6 +3494,109 @@ 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);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Weather Settings
|
||||||
|
document.getElementById('setting-weather-enabled')?.addEventListener('change', (e) => {
|
||||||
|
this.updateSetting('weather.enabled', e.target.checked);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('setting-weather-location')?.addEventListener('change', (e) => {
|
||||||
|
this.updateSetting('weather.location', e.target.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('setting-weather-lat')?.addEventListener('change', (e) => {
|
||||||
|
this.updateSetting('weather.lat', e.target.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('setting-weather-lon')?.addEventListener('change', (e) => {
|
||||||
|
this.updateSetting('weather.lon', e.target.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('setting-weather-interval')?.addEventListener('change', (e) => {
|
||||||
|
this.updateSetting('weather.update_interval', parseInt(e.target.value));
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('setting-weather-units')?.addEventListener('change', (e) => {
|
||||||
|
this.updateSetting('weather.units', e.target.value);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
showNotification: function(message, type) {
|
showNotification: function(message, type) {
|
||||||
@@ -3135,6 +3836,96 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- WEATHER AUTO-UPDATE -->
|
||||||
|
<script>
|
||||||
|
const WeatherUpdater = {
|
||||||
|
updateInterval: <?php echo $settingsManager->getWeatherUpdateInterval() * 60 * 1000; ?>, // Minuten -> Millisekunden
|
||||||
|
|
||||||
|
init: function() {
|
||||||
|
if (!document.getElementById('weather-widget')) return;
|
||||||
|
|
||||||
|
// Update alle X Minuten
|
||||||
|
setInterval(() => this.updateWeather(), this.updateInterval);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateWeather: function() {
|
||||||
|
fetch(window.location.href + '?weather_action=get')
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success && data.data && !data.data.error) {
|
||||||
|
this.renderWeather(data.data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => console.error('Weather update error:', err));
|
||||||
|
},
|
||||||
|
|
||||||
|
renderWeather: function(weather) {
|
||||||
|
const widget = document.getElementById('weather-widget');
|
||||||
|
if (!widget) return;
|
||||||
|
|
||||||
|
const rainSnow = weather.rain_1h > 0 || weather.snow_1h > 0;
|
||||||
|
const precipIcon = weather.rain_1h > 0 ? '🌧️' : '❄️';
|
||||||
|
const precipValue = weather.rain_1h > 0 ? weather.rain_1h : weather.snow_1h;
|
||||||
|
|
||||||
|
widget.innerHTML = `
|
||||||
|
<div class="weather-item weather-temp">
|
||||||
|
<span class="weather-icon">🌡️</span>
|
||||||
|
<span class="weather-value">${weather.temp}°C</span>
|
||||||
|
<span class="weather-label">Temperatur</span>
|
||||||
|
</div>
|
||||||
|
<div class="weather-item weather-wind">
|
||||||
|
<span class="weather-icon">💨</span>
|
||||||
|
<span class="weather-value">${weather.wind_speed} km/h ${weather.wind_direction}</span>
|
||||||
|
<span class="weather-label">Wind</span>
|
||||||
|
</div>
|
||||||
|
<div class="weather-item weather-pressure">
|
||||||
|
<span class="weather-icon">🔽</span>
|
||||||
|
<span class="weather-value">${weather.pressure} hPa</span>
|
||||||
|
<span class="weather-label">Luftdruck</span>
|
||||||
|
</div>
|
||||||
|
<div class="weather-item weather-humidity">
|
||||||
|
<span class="weather-icon">💧</span>
|
||||||
|
<span class="weather-value">${weather.humidity}%</span>
|
||||||
|
<span class="weather-label">Luftfeuchtigkeit</span>
|
||||||
|
</div>
|
||||||
|
<div class="weather-item weather-description">
|
||||||
|
<span class="weather-icon">${this.getWeatherEmoji(weather.icon)}</span>
|
||||||
|
<span class="weather-value">${weather.description}</span>
|
||||||
|
<span class="weather-label">${weather.location}</span>
|
||||||
|
</div>
|
||||||
|
${rainSnow ? `
|
||||||
|
<div class="weather-item weather-precipitation">
|
||||||
|
<span class="weather-icon">${precipIcon}</span>
|
||||||
|
<span class="weather-value">${precipValue} mm</span>
|
||||||
|
<span class="weather-label">Niederschlag</span>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Fade-in Animation
|
||||||
|
widget.style.animation = 'weatherFadeIn 0.5s ease';
|
||||||
|
},
|
||||||
|
|
||||||
|
getWeatherEmoji: function(iconCode) {
|
||||||
|
const map = {
|
||||||
|
'01d': '☀️', '01n': '🌙',
|
||||||
|
'02d': '⛅', '02n': '☁️',
|
||||||
|
'03d': '☁️', '03n': '☁️',
|
||||||
|
'04d': '☁️', '04n': '☁️',
|
||||||
|
'09d': '🌧️', '09n': '🌧️',
|
||||||
|
'10d': '🌦️', '10n': '🌧️',
|
||||||
|
'11d': '⛈️', '11n': '⛈️',
|
||||||
|
'13d': '❄️', '13n': '❄️',
|
||||||
|
'50d': '🌫️', '50n': '🌫️'
|
||||||
|
};
|
||||||
|
return map[iconCode] || '🌤️';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
WeatherUpdater.init();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"viewer_display": {
|
"viewer_display": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"min_viewers": 1
|
"min_viewers": 1,
|
||||||
|
"update_interval": 5
|
||||||
},
|
},
|
||||||
"video_mode": {
|
"video_mode": {
|
||||||
"play_in_player": true,
|
"play_in_player": true,
|
||||||
@@ -9,7 +10,50 @@
|
|||||||
},
|
},
|
||||||
"timelapse": {
|
"timelapse": {
|
||||||
"default_speed": 1,
|
"default_speed": 1,
|
||||||
"available_speeds": [1, 10, 100]
|
"available_speeds": [
|
||||||
|
1,
|
||||||
|
10,
|
||||||
|
100
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ui_display": {
|
||||||
|
"show_recommendation_banner": true,
|
||||||
|
"show_qr_code": true,
|
||||||
|
"show_social_media": true,
|
||||||
|
"show_patrouille_suisse": true
|
||||||
|
},
|
||||||
|
"zoom_timelapse": {
|
||||||
|
"show_zoom_controls": true,
|
||||||
|
"max_zoom_level": 4.0,
|
||||||
|
"timelapse_reverse_enabled": true
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"guestbook_enabled": true,
|
||||||
|
"gallery_enabled": true,
|
||||||
|
"ai_events_enabled": true,
|
||||||
|
"max_guestbook_entries": 50
|
||||||
|
},
|
||||||
|
"technical": {
|
||||||
|
"viewer_update_interval": 5,
|
||||||
|
"session_timeout": 30
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"default_theme": "theme-legacy",
|
||||||
|
"show_theme_switcher": false
|
||||||
|
},
|
||||||
|
"seo": {
|
||||||
|
"custom_title": "",
|
||||||
|
"meta_description": "",
|
||||||
|
"meta_keywords": ""
|
||||||
|
},
|
||||||
|
"weather": {
|
||||||
|
"enabled": true,
|
||||||
|
"api_key": "",
|
||||||
|
"location": "Oberdürnten,CH",
|
||||||
|
"lat": "47.2833",
|
||||||
|
"lon": "8.7167",
|
||||||
|
"update_interval": 5,
|
||||||
|
"units": "metric"
|
||||||
},
|
},
|
||||||
"last_updated": null,
|
"last_updated": null,
|
||||||
"updated_by": null
|
"updated_by": null
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
// Fehler anzeigen
|
||||||
|
error_reporting(E_ALL);
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
|
||||||
|
echo "Test 1: Settings Manager laden...<br>";
|
||||||
|
require_once 'SettingsManager.php';
|
||||||
|
echo "✓ SettingsManager.php geladen<br>";
|
||||||
|
|
||||||
|
echo "Test 2: Weather Manager laden...<br>";
|
||||||
|
require_once 'WeatherManager.php';
|
||||||
|
echo "✓ WeatherManager.php geladen<br>";
|
||||||
|
|
||||||
|
echo "Test 3: SettingsManager initialisieren...<br>";
|
||||||
|
$settingsManager = new SettingsManager();
|
||||||
|
echo "✓ SettingsManager initialisiert<br>";
|
||||||
|
|
||||||
|
echo "Test 4: WeatherManager initialisieren...<br>";
|
||||||
|
$weatherManager = new WeatherManager($settingsManager);
|
||||||
|
echo "✓ WeatherManager initialisiert<br>";
|
||||||
|
|
||||||
|
echo "Test 5: Wetter abrufen...<br>";
|
||||||
|
$weather = $weatherManager->getCurrentWeather();
|
||||||
|
echo "✓ Wetter abgerufen<br>";
|
||||||
|
|
||||||
|
echo "<pre>";
|
||||||
|
print_r($weather);
|
||||||
|
echo "</pre>";
|
||||||
|
|
||||||
|
echo "<br><br>✅ ALLE TESTS ERFOLGREICH!";
|
||||||
|
?>
|
||||||
Reference in New Issue
Block a user