From 9e645a384d4815ab05286060a0e754081cac7837 Mon Sep 17 00:00:00 2001 From: Metacube Date: Mon, 17 Nov 2025 21:38:59 +0100 Subject: [PATCH] Update index.php --- index.php | 961 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 747 insertions(+), 214 deletions(-) diff --git a/index.php b/index.php index 0f68ebd..3fe33f0 100644 --- a/index.php +++ b/index.php @@ -1,8 +1,11 @@ '; - - - } + public function displayWebcam() { + return ''; +} + public function captureSnapshot() { @@ -143,7 +154,7 @@ class WebcamManager { public function getImageFiles() { // Screenshots aus dem image/ Ordner holen $imageFiles = glob("image/screenshot_*.jpg"); - rsort($imageFiles); // Neueste zuerst + sort($imageFiles); // Neueste zuerst return json_encode($imageFiles); } @@ -183,34 +194,56 @@ public function getImageFiles() { unlink($outputFile); exit; } - public function getJavaScript() { +public function getJavaScript() { return " document.addEventListener('DOMContentLoaded', function () { var video = document.getElementById('webcam-player'); - video.controls = false; // Versteckt alle Controls inkl. Play/Pause - var videoSrc = '{$this->videoSrc}'; - if (Hls.isSupported()) { + // Mobile Detection + var isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); + var isIOS = /iPhone|iPad|iPod/i.test(navigator.userAgent); + + // Controls NUR auf Desktop verstecken + video.controls = false; + + if (isIOS) { + // iOS native HLS + video.src = videoSrc; + video.setAttribute('playsinline', ''); + video.setAttribute('webkit-playsinline', ''); + video.muted = true; + + video.addEventListener('loadedmetadata', function() { + video.play().catch(function(e) { + console.log('iOS Autoplay blockiert'); + }); + }); + + } else if (Hls.isSupported()) { var hls = new Hls({ - // WICHTIG: Live-Stream-Einstellungen - liveSyncDurationCount: 3, // Halte 3 Segmente Abstand zum Live-Edge - liveMaxLatencyDurationCount: 10, // Max 10 Segmente hinter Live - liveDurationInfinity: true, // Stream hat kein Ende - enableWorker: true, - lowLatencyMode: false, // Stabilität vor Latenz - backBufferLength: 90, // 90 Sekunden Back-Buffer - maxBufferLength: 60, // 60 Sekunden Forward-Buffer - maxMaxBufferLength: 120, // Max 120 Sekunden - maxBufferSize: 120*1000*1000, // 120MB Buffer + // Mobile-optimierte Einstellungen + liveSyncDurationCount: isMobile ? 2 : 3, + liveMaxLatencyDurationCount: isMobile ? 5 : 10, + liveDurationInfinity: true, + enableWorker: !isMobile, + lowLatencyMode: false, + backBufferLength: isMobile ? 30 : 90, + maxBufferLength: isMobile ? 30 : 60, + maxMaxBufferLength: isMobile ? 60 : 120, + maxBufferSize: isMobile ? 60*1000*1000 : 120*1000*1000, - // Starlink-Optimierungen - manifestLoadingTimeOut: 10000, - manifestLoadingMaxRetry: 5, - levelLoadingTimeOut: 10000, - levelLoadingMaxRetry: 5, - fragLoadingTimeOut: 10000, - fragLoadingMaxRetry: 5 + // Mobile-spezifische Timeouts + manifestLoadingTimeOut: isMobile ? 20000 : 10000, + manifestLoadingMaxRetry: 8, + levelLoadingTimeOut: isMobile ? 20000 : 10000, + levelLoadingMaxRetry: 8, + fragLoadingTimeOut: isMobile ? 20000 : 10000, + fragLoadingMaxRetry: 8, + + // Qualität für Mobile + startLevel: isMobile ? 0 : -1, + abrEwmaDefaultEstimate: isMobile ? 500000 : 1000000 }); hls.loadSource(videoSrc); @@ -219,10 +252,14 @@ public function getImageFiles() { hls.on(Hls.Events.MANIFEST_PARSED, function () { console.log('Stream geladen'); - // WICHTIG: Springe zum Live-Edge minus 60 Sekunden + if (isMobile) { + video.muted = true; + } + + // Live-Position anpassen if (hls.liveSyncPosition !== null) { var targetPosition = hls.liveSyncPosition - 60; - console.log('Setze Position auf: ' + targetPosition + ' (60s vor Live)'); + console.log('Setze Position auf: ' + targetPosition); video.currentTime = targetPosition; } @@ -231,17 +268,6 @@ public function getImageFiles() { }); }); - // Überwache den Live-Sync - hls.on(Hls.Events.LEVEL_UPDATED, function(event, data) { - console.log('Stream aktualisiert, neue Segmente verfügbar'); - - // Wenn wir zu weit zurückfallen, springe vor - if (video.currentTime < hls.liveSyncPosition - 120) { - console.log('Zu weit zurück, springe näher zum Live-Edge'); - video.currentTime = hls.liveSyncPosition - 60; - } - }); - // Fehlerbehandlung hls.on(Hls.Events.ERROR, function(event, data) { if (data.fatal) { @@ -265,35 +291,11 @@ public function getImageFiles() { } }); - // Automatisches Nachladen erzwingen - setInterval(function() { - if (!video.paused && !video.seeking) { - // Prüfe ob neue Segmente verfügbar sind - if (hls.levels && hls.levels.length > 0) { - var level = hls.levels[hls.currentLevel]; - if (level && level.details && level.details.live) { - console.log('Live-Stream läuft, Edge bei: ' + level.details.edge); - - // Wenn wir am Ende sind, lade neue Segmente - var bufferEnd = 0; - if (video.buffered.length > 0) { - bufferEnd = video.buffered.end(video.buffered.length - 1); - } - - if (bufferEnd - video.currentTime < 10) { - console.log('Buffer niedrig, lade neue Segmente...'); - hls.startLoad(); - } - } - } - } - }, 5000); // Alle 5 Sekunden prüfen - } else if (video.canPlayType('application/vnd.apple.mpegurl')) { - // Safari/iOS Fallback + // Fallback für andere Browser video.src = videoSrc; + video.muted = true; video.addEventListener('loadedmetadata', function () { - // Für Safari: Setze currentTime zurück für Pseudo-DVR video.currentTime = Math.max(0, video.duration - 60); video.play(); }); @@ -307,6 +309,7 @@ public function getImageFiles() { + public function setVideoSrc($src) { $this->videoSrc = $src; } @@ -325,7 +328,7 @@ class VisualCalendarManager { private $videoDir; private $monthNames; - public function __construct($videoDir = './image/') { + public function __construct($videoDir = './videos/') { $this->videoDir = $videoDir; $this->monthNames = [ 1 => ['de' => 'Januar', 'en' => 'January', 'it' => 'Gennaio', 'fr' => 'Janvier', 'zh' => '一月'], @@ -575,56 +578,291 @@ public function displayEntries($isAdmin = false) { } + class ContactManager { + //private $adminEmail = 'ingo.kohler.zh@gmail.com'; // ← Empfänger + private $adminEmail = 'metacube@gmail.com'; // ← Empfänger + private $feedbackFile = 'feedbacks.json'; + private $gmailUser = 'metacube@gmail.com'; // ← DEINE GMAIL-ADRESSE + private $gmailAppPassword = 'qggk hsxz fdkq jgxa'; // ← APP-PASSWORT VON GMAIL + public function displayForm() { return ' -
+ - - - - - - - - -
'; + + + + + + + + + + + +
'; } public function handleSubmission($name, $email, $message) { + // Validierung + if (empty($name) || empty($email) || empty($message)) { + return [ + 'success' => false, + 'message' => 'Alle Felder sind erforderlich' + ]; + } + + if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { + return [ + 'success' => false, + 'message' => 'Ungültige E-Mail-Adresse' + ]; + } + + if (strlen($message) < 10) { + return [ + 'success' => false, + 'message' => 'Nachricht zu kurz (mindestens 10 Zeichen)' + ]; + } + + // Sanitize + $name = htmlspecialchars(trim($name), ENT_QUOTES, 'UTF-8'); + $email = filter_var(trim($email), FILTER_SANITIZE_EMAIL); + $message = htmlspecialchars(trim($message), ENT_QUOTES, 'UTF-8'); + + // Feedback speichern $feedback = [ 'name' => $name, 'email' => $email, 'message' => $message, - 'date' => date('Y-m-d H:i:s') + 'date' => date('Y-m-d H:i:s'), + 'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown', + 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown' ]; - $feedbacks = json_decode(file_get_contents('feedbacks.json') ?: '[]', true); + + $feedbacks = file_exists($this->feedbackFile) + ? json_decode(file_get_contents($this->feedbackFile), true) + : []; + + if (!is_array($feedbacks)) { + $feedbacks = []; + } + $feedbacks[] = $feedback; - file_put_contents('feedbacks.json', json_encode($feedbacks)); + file_put_contents($this->feedbackFile, json_encode($feedbacks, JSON_PRETTY_PRINT)); + + // E-MAIL SENDEN MIT GMAIL SMTP + $mailSent = $this->sendEmailViaGmail($name, $email, $message, $feedback['date'], $feedback['ip']); + + if ($mailSent) { + return [ + 'success' => true, + 'message' => 'Vielen Dank! Ihre Nachricht wurde gesendet.' + ]; + } else { + error_log("Mail-Fehler: Nachricht von {$email} konnte nicht gesendet werden"); + return [ + 'success' => false, + 'message' => 'Nachricht wurde gespeichert, aber E-Mail konnte nicht gesendet werden.' + ]; + } + } + + private function sendEmailViaGmail($name, $email, $message, $date, $ip) { + $mail = new PHPMailer(true); + + + try { + // DEBUG-MODUS AKTIVIEREN + $mail->SMTPDebug = 2; + $mail->Debugoutput = function($str, $level) { + error_log("PHPMailer Debug: $str"); + }; + + // SMTP Konfiguration + $mail->isSMTP(); + $mail->Host = 'smtp.gmail.com'; + $mail->SMTPAuth = true; + $mail->Username = $this->gmailUser; // metacube@gmail.com + $mail->Password = $this->gmailAppPassword; // qggk hsxz fdkq jgxa + $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; + $mail->Port = 587; + + + + + // Absender & Empfänger + $mail->setFrom($this->gmailUser, 'Aurora Livecam'); + $mail->addAddress($this->adminEmail); // admin@aurora-live-weathercam.com + $mail->addReplyTo($email, $name); + + // Inhalt + $mail->isHTML(true); + $mail->CharSet = 'UTF-8'; + $mail->Subject = '🔔 Neue Kontaktanfrage von ' . $name; + $mail->Body = $this->getEmailTemplate($name, $email, $message, $date, $ip); + + $mail->send(); + return true; + } catch (Exception $e) { + error_log("PHPMailer Error: {$mail->ErrorInfo}"); + return false; + } + } + + private function getEmailTemplate($name, $email, $message, $date, $ip) { + return " + + + + + + + +
+
+

📧 Neue Kontaktanfrage

+

Aurora Weather Livecam

+
+ +
+
+ 👤 Name: + {$name} +
+ +
+ 📧 E-Mail: + {$email} +
+ +
+ 💬 Nachricht: +
+ +
+ " . nl2br($message) . " +
+ +
+ + ↩️ Direkt antworten + +
+
+ + +
+ + + "; } } @@ -634,7 +872,7 @@ class AdminManager { } public function handleLogin($username, $password) { echo "Login-Versuch: Username = $username, Passwort = $password"; // Debugging - if ($username === 'admin' && $password === 'sonne4000') { + if ($username === 'admin' && $password === 'sonne4000$$$$Q') { $_SESSION['admin'] = true; return true; } @@ -780,7 +1018,7 @@ class VideoArchiveManager { private $videoDir; private $monthNames; - public function __construct($videoDir = './image/') { + public function __construct($videoDir = './videos/') { $this->videoDir = $videoDir; $this->monthNames = [ '01' => 'Januar', @@ -979,7 +1217,7 @@ $contactManager = new ContactManager(); $adminManager = new AdminManager(); // Nach den anderen Manager-Instanzen hinzufügen -$videoArchiveManager = new VideoArchiveManager('./image/'); +$videoArchiveManager = new VideoArchiveManager('./videos/'); // Video-Download-Handler nach dem existierenden Download-Handler hinzufügen $videoArchiveManager->handleSpecificVideoDownload(); @@ -1019,8 +1257,22 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST[' if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (isset($_POST['guestbook'])) { $guestbookManager->handleFormSubmission(); - } elseif (isset($_POST['contact'])) { - $contactManager->handleSubmission($_POST['name'], $_POST['email'], $_POST['message']); + } elseif (isset($_POST['contact'])) { + $result = $contactManager->handleSubmission($_POST['name'], $_POST['email'], $_POST['message']); + + // JSON-Response für AJAX + if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && + strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') { + header('Content-Type: application/json'); + echo json_encode($result); + exit; + } + + // Normale Formular-Submission + $_SESSION['contact_result'] = $result; + header('Location: ' . $_SERVER['PHP_SELF'] . '#kontakt'); + exit; + } elseif (isset($_POST['admin-login'])) { $adminManager->handleLogin($_POST['username'], $_POST['password']); } elseif (isset($_POST['update-social-media'])) { @@ -1073,6 +1325,24 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { box-shadow: 0 5px 20px rgba(0,0,0,0.1); } + + +#timelapse-time-overlay { + position: absolute; + top: 20px; + left: 20px; + background: rgba(0,0,0,0.7); + color: white; + padding: 10px 15px; + border-radius: 5px; + font-size: 18px; + font-weight: bold; + z-index: 100; + font-family: monospace; + box-shadow: 0 2px 10px rgba(0,0,0,0.5); +} + + .calendar-navigation { display: flex; justify-content: space-between; @@ -1193,6 +1463,40 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } } + +/* Mobile Video Optimierungen */ +@media (max-width: 768px) { + .video-container { + position: relative; + padding-bottom: 56.25%; + height: 0; + overflow: hidden; + } + + #webcam-player { + position: absolute; + top: 0; + left: 0; + width: 100% !important; + height: 100% !important; + object-fit: contain; + -webkit-tap-highlight-color: transparent; + -webkit-playsinline: true; + } +} + +/* iOS-spezifische Fixes */ +@supports (-webkit-touch-callout: none) { + #webcam-player { + -webkit-playsinline: true; + -webkit-video-playable-inline: true; + } +} + + + + + .day-videos h4 { color: #333; margin-bottom: 15px; @@ -1814,13 +2118,23 @@ label { .modal-content { margin: auto; display: block; - width: 80%; - max-width: 700px; - max-height: 80vh; - object-fit: contain; + width: 95vw; /* 95% Viewport-Breite */ + max-width: none; /* Keine Begrenzung */ + max-height: 90vh; /* 90% Viewport-Höhe */ + object-fit: contain; /* Seitenverhältnis beibehalten */ border-radius: 5px; } +/* Mobile Optimierung */ +@media (max-width: 768px) { + .modal-content { + width: 98vw; + max-height: 85vh; + touch-action: pinch-zoom; /* Zoom/Pinch erlauben */ + } +} + + #caption { margin: 15px auto; display: block; @@ -2100,6 +2414,10 @@ footer {
+ + + +
- - - - - - - - -isAdmin()): ?> -
-

✅ Heutige Events bestätigen (Admin)

-

Klicke auf die Events, die heute eingetreten sind:

-
- getTodaysChallenges(); - foreach ($todaysChallenges as $event): - ?> - - -
-
-
- - -
- - + @@ -2347,7 +2601,7 @@ footer { - - + --> @@ -2717,7 +2987,7 @@ footer { Webcam Gästebuch Kontakt - + Galerie Impressum @@ -2863,6 +3133,100 @@ document.addEventListener('DOMContentLoaded', function() { }); +// ========== ADMIN TOGGLE FÜR SUNRISE/SUNSET SECTION ========== +(function() { + const adminToggleKey = 'sunriseSunsetAdminVisible'; + + // Admin-Status aus localStorage laden + let isVisible = localStorage.getItem(adminToggleKey) !== 'false'; + + // Toggle-Button erstellen + function createToggleButton() { + const section = document.getElementById('sunrise-sunset'); + if (!section) { + console.log('❌ Section #sunrise-sunset nicht gefunden'); + return; + } + + const toggleBtn = document.createElement('button'); + toggleBtn.id = 'sunrise-sunset-toggle'; + toggleBtn.innerHTML = isVisible ? '👁️ Ausblenden' : '👁️‍🗨️ Einblenden'; + toggleBtn.style.cssText = ` + position: fixed; + bottom: 20px; + right: 20px; + z-index: 9999; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + padding: 12px 20px; + border-radius: 25px; + cursor: pointer; + font-weight: bold; + box-shadow: 0 4px 15px rgba(0,0,0,0.3); + transition: all 0.3s; + `; + + toggleBtn.addEventListener('mouseenter', function() { + this.style.transform = 'scale(1.1)'; + }); + + toggleBtn.addEventListener('mouseleave', function() { + this.style.transform = 'scale(1)'; + }); + + toggleBtn.addEventListener('click', function() { + isVisible = !isVisible; + localStorage.setItem(adminToggleKey, isVisible); + updateVisibility(); + this.innerHTML = isVisible ? '👁️ Ausblenden' : '👁️‍🗨️ Einblenden'; + console.log('✅ Sichtbarkeit geändert:', isVisible); + }); + + document.body.appendChild(toggleBtn); + console.log('✅ Toggle-Button erstellt'); + } + + // Sichtbarkeit aktualisieren + function updateVisibility() { + const section = document.getElementById('sunrise-sunset'); + if (section) { + section.style.display = isVisible ? 'block' : 'none'; + console.log('✅ Section Sichtbarkeit:', isVisible ? 'sichtbar' : 'versteckt'); + } + } + + // Button IMMER erstellen (keine PHP-Bedingung) + createToggleButton(); + updateVisibility(); +})(); +// ========== ENDE ADMIN TOGGLE ========== + + + +let currentImageIndex = 0; +const galleryImages = Array.from(document.querySelectorAll('#gallery-images img')); + +document.addEventListener('keydown', function(e) { + if (modal.style.display === 'block') { + if (e.key === 'ArrowRight') showNextImage(); + if (e.key === 'ArrowLeft') showPrevImage(); + if (e.key === 'Escape') modal.style.display = 'none'; + } +}); + +function showNextImage() { + currentImageIndex = (currentImageIndex + 1) % galleryImages.length; + modalImg.src = galleryImages[currentImageIndex].src; +} + +function showPrevImage() { + currentImageIndex = (currentImageIndex - 1 + galleryImages.length) % galleryImages.length; + modalImg.src = galleryImages[currentImageIndex].src; +} + + + function toggleTimelapse() { if (timelapseViewer.style.display === 'none') { timelapseViewer.style.display = 'block'; @@ -2875,7 +3239,7 @@ document.addEventListener('DOMContentLoaded', function() { stopTimelapse(); timelapseViewer.style.display = 'none'; webcamPlayer.style.display = 'block'; - timelapseButton.textContent = 'Tageszeitraffer anzeigen'; + timelapseButton.textContent = 'Wochenzeitraffer'; } } @@ -2900,6 +3264,36 @@ document.addEventListener('DOMContentLoaded', function() { const currentImage = imageFiles[currentImageIndex]; console.log(`Verarbeite Bild: ${currentImage}`); + const filename = currentImage.split('/').pop(); + const timeMatch = filename.match(/screenshot_(\d{4})(\d{2})(\d{2})_(\d{2})(\d{2})(\d{2})/); + if (timeMatch) { + const [_, year, month, day, hour, minute, second] = timeMatch; + const dateStr = `${day}.${month}.${year} ${hour}:${minute}:${second}`; + + // Zeit-Overlay erstellen oder aktualisieren + let timeOverlay = document.getElementById('timelapse-time-overlay'); + if (!timeOverlay) { + timeOverlay = document.createElement('div'); + timeOverlay.id = 'timelapse-time-overlay'; + timeOverlay.style.cssText = ` + position: absolute; + top: 20px; + left: 20px; + background: rgba(0,0,0,0.7); + color: white; + padding: 10px 15px; + border-radius: 5px; + font-size: 18px; + font-weight: bold; + z-index: 100; + font-family: monospace; + `; + document.getElementById('timelapse-viewer').appendChild(timeOverlay); + } + timeOverlay.textContent = dateStr; + } + + // Lazy Loading for (let i = currentImageIndex; i < currentImageIndex + preloadBuffer && i < imageFiles.length; i++) { if (!imageCache.has(imageFiles[i])) { @@ -3259,6 +3653,89 @@ document.addEventListener('DOMContentLoaded', function() { + + + + + + + + + + + + + + + + +