{"id":7734,"date":"2025-10-27T11:27:42","date_gmt":"2025-10-27T15:27:42","guid":{"rendered":"https:\/\/spacehaven.space\/spaces\/?page_id=7734"},"modified":"2025-10-27T22:49:02","modified_gmt":"2025-10-28T02:49:02","slug":"skycam-v2-0","status":"publish","type":"page","link":"https:\/\/spacehaven.space\/spaces\/skycam-v2-0\/","title":{"rendered":"SkyCam-v2.0"},"content":{"rendered":"\n<div class=\"skycam-hero\">\n  <video id=\"skycam-video\" muted playsinline preload=\"auto\" poster=\"https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/startrails-20251016.jpg\">\n    <source src=\"https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/Skycam-7seconds.mp4\" type=\"video\/mp4\">\n  <\/video>\n  <div class=\"poster-overlay\" id=\"poster-overlay\">\n    <img decoding=\"async\" src=\"https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/startrails-20251016.jpg\" alt=\"Star Trails\">\n  <\/div>\n  <div class=\"hero-overlay\" id=\"hero-overlay\">\n    <h1>SkyCam Live View<\/h1>\n    <p>24-hour time-lapse of the sky above Space Haven Observatory<\/p>\n  <\/div>\n  <div class=\"controls-container\">\n    <div class=\"video-controls\">\n      <button id=\"rewind-btn\" class=\"control-btn\" aria-label=\"Rewind 2 seconds\" title=\"Rewind 2 seconds\">\n        <div class=\"rewind-icon\"><\/div>\n      <\/button>\n      <button id=\"play-pause-btn\" class=\"control-btn\" aria-label=\"Play\/Pause\">\n        <div class=\"play-icon\" style=\"display: none;\"><\/div>\n        <div class=\"pause-icon\"><\/div>\n      <\/button>\n    <\/div>\n    <div class=\"progress-bar-container\">\n      <div class=\"progress-bar\" id=\"progress-bar\">\n        <div class=\"progress-fill\" id=\"progress-fill\"><\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/div>\n\n<style>\n.skycam-hero {\n  position: relative;\n  width: 100%;\n  height: 600px;\n  overflow: hidden;\n  margin: 0;\n}\n\n.skycam-hero video {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  object-fit: cover;\n}\n\n.skycam-hero .poster-overlay {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  z-index: 5;\n  background: black;\n}\n\n.skycam-hero .poster-overlay img {\n  width: 100%;\n  height: 100%;\n  object-fit: cover;\n}\n\n.skycam-hero .hero-overlay {\n  position: relative;\n  z-index: 10;\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  align-items: center;\n  height: 100%;\n  background: rgba(0,0,0,0.5);\n  transition: background 0.3s ease;\n  padding: 20px;\n  text-align: center;\n}\n\n.skycam-hero .hero-overlay.video-paused {\n  background: rgba(0,0,0,0.3);\n}\n\n.skycam-hero h1 {\n  color: white;\n  font-size: 3rem;\n  margin: 0 0 1rem 0;\n  text-shadow: 2px 2px 4px rgba(0,0,0,0.8);\n}\n\n.skycam-hero p {\n  color: white;\n  font-size: 1.3rem;\n  margin: 0;\n  text-shadow: 1px 1px 2px rgba(0,0,0,0.8);\n  max-width: 90%;\n}\n\n.skycam-hero .datetime {\n  font-size: 1.1rem;\n  margin-top: 1rem;\n  opacity: 0.9;\n}\n\n\/* Mobile responsive - text only *\/\n@media (max-width: 768px) {\n  .skycam-hero {\n    height: 500px;\n  }\n  \n  .skycam-hero h1 {\n    font-size: 2rem;\n  }\n  \n  .skycam-hero p:not(.datetime) {\n    display: none;\n  }\n  \n  .skycam-hero .datetime {\n    position: absolute;\n    bottom: 30px;\n    right: 140px;\n    left: 20px;\n    margin: 0;\n    text-align: right;\n    font-size: 0.75rem;\n    z-index: 20;\n  }\n}\n\n@media (max-width: 480px) {\n  .skycam-hero {\n    height: 400px;\n  }\n  \n  .skycam-hero h1 {\n    font-size: 1.5rem;\n  }\n  \n  .skycam-hero p {\n    font-size: 0.9rem;\n  }\n  \n  .skycam-hero .datetime {\n    font-size: 0.75rem;\n  }\n}\n\n\/* Controls - same on all devices *\/\n.controls-container {\n  position: absolute;\n  bottom: 20px;\n  right: 20px;\n  z-index: 20;\n  display: flex;\n  flex-direction: column;\n  gap: 8px;\n  align-items: center;\n}\n\n.video-controls {\n  display: flex;\n  gap: 10px;\n}\n\n.control-btn {\n  background: rgba(255, 255, 255, 0.85);\n  border: none;\n  border-radius: 8px;\n  width: 45px;\n  height: 45px;\n  font-size: 22px;\n  cursor: pointer;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  transition: all 0.2s ease;\n  box-shadow: 0 2px 4px rgba(0,0,0,0.3);\n  -webkit-tap-highlight-color: transparent;\n  -webkit-appearance: none;\n  -moz-appearance: none;\n  appearance: none;\n  color: #000;\n}\n\n\/* CSS Shape: Play Triangle *\/\n.play-icon {\n  width: 0;\n  height: 0;\n  border-left: 18px solid currentColor;\n  border-top: 12px solid transparent;\n  border-bottom: 12px solid transparent;\n  margin-left: 3px; \/* Visual centering *\/\n}\n\n\/* CSS Shape: Pause Bars *\/\n.pause-icon {\n  display: flex;\n  gap: 5px;\n  align-items: center;\n  justify-content: center;\n}\n\n.pause-icon::before,\n.pause-icon::after {\n  content: '';\n  width: 5px;\n  height: 24px;\n  background: currentColor;\n  border-radius: 1px;\n  display: block;\n  flex-shrink: 0;\n}\n\n\/* CSS Shape: Rewind (double left triangles) *\/\n.rewind-icon {\n  display: flex;\n  gap: 2px;\n}\n\n.rewind-icon::before,\n.rewind-icon::after {\n  content: '';\n  width: 0;\n  height: 0;\n  border-right: 12px solid currentColor;\n  border-top: 9px solid transparent;\n  border-bottom: 9px solid transparent;\n}\n\n.control-btn:hover {\n  background: rgba(255, 255, 255, 1);\n  transform: scale(1.05);\n}\n\n.control-btn:active {\n  transform: scale(0.95);\n}\n\n.progress-bar-container {\n  width: 100px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.progress-bar {\n  flex: 1;\n  height: 4px;\n  background: rgba(255, 255, 255, 0.3);\n  border-radius: 2px;\n  cursor: pointer;\n  position: relative;\n  overflow: hidden;\n}\n\n.progress-bar:hover {\n  height: 6px;\n}\n\n.progress-fill {\n  height: 100%;\n  background: rgba(255, 255, 255, 0.9);\n  width: 0%;\n  border-radius: 2px;\n  transition: width 0.1s linear;\n}\n<\/style>\n\n<script>\ndocument.addEventListener('DOMContentLoaded', function() {\n    const videoUrl = 'https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/Skycam-7seconds.mp4';\n    const video = document.getElementById('skycam-video');\n    const playPauseBtn = document.getElementById('play-pause-btn');\n    const rewindBtn = document.getElementById('rewind-btn');\n    const playIcon = playPauseBtn.querySelector('.play-icon');\n    const pauseIcon = playPauseBtn.querySelector('.pause-icon');\n    const overlay = document.getElementById('hero-overlay');\n    const posterOverlay = document.getElementById('poster-overlay');\n    const progressBar = document.getElementById('progress-bar');\n    const progressFill = document.getElementById('progress-fill');\n    \n    \/\/ Update video source\n    if(video) {\n        const source = video.querySelector('source');\n        source.src = videoUrl + '?t=' + new Date().getTime();\n        video.load();\n        \n        \/\/ Ensure poster is visible initially\n        posterOverlay.style.display = 'block';\n        \n        \/\/ Delay 2 seconds to show poster, then autoplay\n        setTimeout(function() {\n            video.play().then(() => {\n                \/\/ Video started successfully\n                playIcon.style.display = 'none';\n                pauseIcon.style.display = 'flex';\n                overlay.classList.remove('video-paused');\n                posterOverlay.style.display = 'none';\n            }).catch(() => {\n                \/\/ If autoplay fails, show play button but keep poster visible\n                playIcon.style.display = 'block';\n                pauseIcon.style.display = 'none';\n                overlay.classList.add('video-paused');\n            });\n        }, 2000);\n        \n        \/\/ Update button state when video plays\/pauses\n        video.addEventListener('play', function() {\n            playIcon.style.display = 'none';\n            pauseIcon.style.display = 'flex';\n            overlay.classList.remove('video-paused');\n            posterOverlay.style.display = 'none';\n        });\n        \n        video.addEventListener('pause', function() {\n            playIcon.style.display = 'block';\n            pauseIcon.style.display = 'none';\n            overlay.classList.add('video-paused');\n        });\n        \n        \/\/ Handle video end - show poster and play button\n        video.addEventListener('ended', function() {\n            playIcon.style.display = 'block';\n            pauseIcon.style.display = 'none';\n            overlay.classList.add('video-paused');\n            posterOverlay.style.display = 'block';\n        });\n    }\n    \n    \/\/ Play\/Pause functionality\n    playPauseBtn.addEventListener('click', function() {\n        if (video.paused) {\n            \/\/ If video ended, restart from beginning\n            if (video.ended) {\n                video.currentTime = 0;\n            }\n            video.play();\n            playIcon.style.display = 'none';\n            pauseIcon.style.display = 'flex';\n            overlay.classList.remove('video-paused');\n            posterOverlay.style.display = 'none';\n        } else {\n            video.pause();\n            playIcon.style.display = 'block';\n            pauseIcon.style.display = 'none';\n            overlay.classList.add('video-paused');\n        }\n    });\n    \n    \/\/ Rewind 2 seconds\n    rewindBtn.addEventListener('click', function() {\n        video.currentTime = Math.max(0, video.currentTime - 2);\n        if (video.paused) {\n            video.play();\n            playIcon.style.display = 'none';\n            pauseIcon.style.display = 'flex';\n            overlay.classList.remove('video-paused');\n            posterOverlay.style.display = 'none';\n        }\n    });\n    \n    \/\/ Update progress bar as video plays\n    video.addEventListener('timeupdate', function() {\n        const progress = (video.currentTime \/ video.duration) * 100;\n        progressFill.style.width = progress + '%';\n    });\n    \n    \/\/ Click on progress bar to seek\n    progressBar.addEventListener('click', function(e) {\n        const rect = progressBar.getBoundingClientRect();\n        const pos = (e.clientX - rect.left) \/ rect.width;\n        video.currentTime = pos * video.duration;\n    });\n    \n    \/\/ Touch support for progress bar on mobile\n    progressBar.addEventListener('touchstart', function(e) {\n        e.preventDefault();\n        const touch = e.touches[0];\n        const rect = progressBar.getBoundingClientRect();\n        const pos = (touch.clientX - rect.left) \/ rect.width;\n        video.currentTime = pos * video.duration;\n    });\n    \n    \/\/ Get video timestamp\n    fetch(videoUrl, { method: 'HEAD' })\n        .then(response => {\n            const lastModified = response.headers.get('Last-Modified');\n            if (lastModified) {\n                const videoDate = new Date(lastModified);\n                const options = { \n                    weekday: 'long', \n                    year: 'numeric', \n                    month: 'long', \n                    day: 'numeric',\n                    hour: '2-digit',\n                    minute: '2-digit',\n                    timeZoneName: 'short'\n                };\n                const dateTimeString = videoDate.toLocaleString('en-US', options);\n                document.getElementById('video-datetime').textContent = 'Video captured: ' + dateTimeString;\n            } else {\n                document.getElementById('video-datetime').textContent = 'Video timestamp unavailable';\n            }\n        })\n        .catch(() => {\n            \/\/ Silently handle error - timestamp display will show loading message\n            document.getElementById('video-datetime').textContent = 'Video timestamp unavailable';\n        });\n});\n<\/script>\n\n\n\n<div class=\"wp-block-group alignfull has-global-padding is-layout-constrained wp-block-group-is-layout-constrained\" style=\"padding-top:var(--wp--preset--spacing--small);padding-bottom:var(--wp--preset--spacing--small)\">\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-7db7aa3e wp-block-columns-is-layout-flex\" style=\"padding-right:0;padding-left:0\">\n<div class=\"wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow\">\n<h2 class=\"wp-block-heading has-medium-font-size\" style=\"text-transform:uppercase\">SKycam<\/h2>\n\n\n\n<p>The SkyCam recording above is a live 24-hour time-lapse of the sky above the Space Haven Observatory. It updates daily and, depending on conditions, you may witness a variety of events such as sunrise and sunset, lunar phases, star and planet movements, meteor showers, rainfall, and dew formation. On rainy or overcast days, activity may be limited, but there is always something fascinating happening in the sky.<\/p>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-group alignfull has-background has-global-padding is-layout-constrained wp-block-group-is-layout-constrained\" style=\"background-color:#e5e9ec;padding-top:var(--wp--preset--spacing--small);padding-bottom:var(--wp--preset--spacing--small)\">\n<div class=\"wp-block-columns are-vertically-aligned-top is-layout-flex wp-container-core-columns-is-layout-7db7aa3e wp-block-columns-is-layout-flex\" style=\"padding-right:0;padding-left:0\">\n<div class=\"wp-block-column is-vertically-aligned-top is-layout-flow wp-block-column-is-layout-flow\">\n<h2 class=\"wp-block-heading has-medium-font-size\" style=\"text-transform:uppercase\">Live Image<\/h2>\n\n\n\n<img decoding=\"async\" id=\"skycam-live\" src=\"https:\/\/spacehaven.space\/spaces\/allsky\/image.jpg\" alt=\"SkyCam Live Image\" style=\"width: 100%; height: auto;\">\n<h4 style=\"text-align: center; margin-top: 8px; font-family: 'Onest', sans-serif;\">SkyCam Live Image<\/h4>\n<p id=\"skycam-timestamp\" style=\"text-align: center; margin-top: 2px; font-size: 0.9em; font-family: 'Onest', sans-serif;\"><\/p>\n<script>\nfunction updateSkyCam() {\n  var img = document.getElementById('skycam-live');\n  var timestamp = document.getElementById('skycam-timestamp');\n  var baseUrl = 'https:\/\/spacehaven.space\/spaces\/allsky\/image.jpg';\n  \n  \/\/ Update image\n  img.src = baseUrl + '?t=' + new Date().getTime();\n  \n  \/\/ Update timestamp\n  var now = new Date();\n  var options = { \n    year: 'numeric', \n    month: 'long', \n    day: 'numeric', \n    hour: '2-digit', \n    minute: '2-digit', \n    second: '2-digit',\n    timeZoneName: 'short'\n  };\n  var dateString = now.toLocaleString('en-US', options);\n  timestamp.textContent = '(' + dateString + ' - USA)';\n}\n\n\/\/ Initial load\nupdateSkyCam();\n\n\/\/ Auto-refresh every 60 seconds\nsetInterval(updateSkyCam, 60000);\n<\/script>\n\n\n\n<p>This live image displays the current sky above the Space Haven Observatory, updating approximately every 60 seconds (depending on exposure settings). It provides real-time context for our telescope observations, allowing us to correlate cloud cover, atmospheric conditions, and lighting changes with our ongoing astrophotography sessions. If you see an unrecognizable image, it can be caused by condensation, snow or other atmospheric conditions. <\/p>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-vertically-aligned-top is-layout-flow wp-block-column-is-layout-flow\">\n<h2 class=\"wp-block-heading has-medium-font-size\" style=\"text-transform:uppercase\">YESTERDAY&#8217;S Time-Laps<\/h2>\n\n\n\n<div style=\"height:20px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<!-- ===== SkyCam Embed (Responsive, Standalone) ===== -->\n<div class=\"skycam-embed\"\n     data-video=\"https:\/\/spacehaven.space\/spaces\/allsky\/videos\/allsky.mp4\"\n     data-poster=\"https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/startrails-20251016.jpg\"\n     data-autoplay=\"false\"\n     data-delay=\"2000\"\n     data-ratio=\"4:3\">\n\n  <video class=\"sky2-video\" muted playsinline preload=\"auto\"\n         poster=\"https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/startrails-20251016.jpg\">\n    <source src=\"https:\/\/spacehaven.space\/spaces\/allsky\/videos\/allsky.mp4\" type=\"video\/mp4\">\n  <\/video>\n\n  <div class=\"sky2-poster\">\n    <img decoding=\"async\" src=\"https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/startrails-20251016.jpg\" alt=\"SkyCam Poster\">\n  <\/div>\n\n  <div class=\"sky2-overlay\">\n    <p class=\"sky2-datetime\">Loading video timestamp&#8230;<\/p>\n  <\/div>\n\n  <div class=\"sky2-controls-wrap\">\n    <div class=\"sky2-controls\">\n      <button class=\"sky2-btn sky2-rewind\" aria-label=\"Rewind 2 seconds\" title=\"Rewind 2 seconds\">\n        <div class=\"sky2-rewind-icon\"><\/div>\n      <\/button>\n      <button class=\"sky2-btn sky2-playpause\" aria-label=\"Play\/Pause\">\n        <div class=\"sky2-play-icon\" style=\"display:none;\"><\/div>\n        <div class=\"sky2-pause-icon\"><\/div>\n      <\/button>\n    <\/div>\n    <div class=\"sky2-progress-bar\">\n      <div class=\"sky2-progress-fill\"><\/div>\n    <\/div>\n  <\/div>\n<\/div>\n\n<style>\n\/* ===== Scoped SkyCam Embed Styles (Responsive) ===== *\/\n.skycam-embed {\n  \/* Fit container width; height is driven by aspect-ratio *\/\n  width: 100%;\n  aspect-ratio: var(--sky2-aspect, 16\/9);\n  height: auto;\n  position: relative;\n  overflow: hidden;\n  margin: 0;\n}\n\n\/* All layers fill the box determined by aspect-ratio *\/\n.skycam-embed .sky2-video,\n.skycam-embed .sky2-poster,\n.skycam-embed .sky2-overlay {\n  position: absolute;\n  inset: 0;\n}\n\n.skycam-embed .sky2-video {\n  width: 100%; height: 100%; object-fit: cover;\n}\n\n.skycam-embed .sky2-poster { z-index: 5; background: #000; }\n.skycam-embed .sky2-poster img {\n  width: 100%; height: 100%; object-fit: cover;\n}\n\n.skycam-embed .sky2-overlay {\n  z-index: 10;\n  display: flex; align-items: flex-end; justify-content: flex-end;\n  background: rgba(0,0,0,0.5);\n  transition: background 0.3s ease;\n  padding: 20px;\n}\n.skycam-embed .sky2-overlay.video-paused { background: rgba(0,0,0,0.3); }\n\n\/* Datetime: phone-style at all widths (bottom-right) *\/\n.skycam-embed .sky2-datetime {\n  position: absolute;\n  bottom: 30px;\n  right: 140px;  \/* keeps space for controls column *\/\n  left: 20px;\n  margin: 0;\n  text-align: right;\n  color: #fff;\n  font-size: 0.75rem;\n  z-index: 20;\n  opacity: 0.95;\n  text-shadow: 1px 1px 2px rgba(0,0,0,0.8);\n}\n\n\/* Controls block (bottom-right) *\/\n.skycam-embed .sky2-controls-wrap {\n  position: absolute; bottom: 20px; right: 20px;\n  z-index: 20;\n  display: flex; flex-direction: column;\n  gap: 8px; align-items: center;\n  width: 100px; \/* aligns with datetime right offset *\/\n}\n.skycam-embed .sky2-controls { display: flex; gap: 10px; }\n\n.skycam-embed .sky2-btn {\n  background: rgba(255,255,255,0.85);\n  border: none; border-radius: 8px;\n  width: 45px; height: 45px;\n  font-size: 22px; cursor: pointer;\n  display: flex; align-items: center; justify-content: center;\n  transition: all 0.2s ease;\n  box-shadow: 0 2px 4px rgba(0,0,0,0.3);\n  -webkit-tap-highlight-color: transparent;\n  appearance: none; color: #000;\n}\n.skycam-embed .sky2-btn:hover { background: rgba(255,255,255,1); transform: scale(1.05); }\n.skycam-embed .sky2-btn:active { transform: scale(0.95); }\n\n\/* Icons *\/\n.skycam-embed .sky2-play-icon {\n  width: 0; height: 0;\n  border-left: 18px solid currentColor;\n  border-top: 12px solid transparent;\n  border-bottom: 12px solid transparent;\n  margin-left: 3px;\n}\n.skycam-embed .sky2-pause-icon {\n  display: flex; gap: 5px; align-items: center; justify-content: center;\n}\n.skycam-embed .sky2-pause-icon::before,\n.skycam-embed .sky2-pause-icon::after {\n  content: ''; width: 5px; height: 24px; background: currentColor; border-radius: 1px; display: block;\n}\n.skycam-embed .sky2-rewind-icon { display: flex; gap: 2px; }\n.skycam-embed .sky2-rewind-icon::before,\n.skycam-embed .sky2-rewind-icon::after {\n  content: ''; width: 0; height: 0;\n  border-right: 12px solid currentColor;\n  border-top: 9px solid transparent;\n  border-bottom: 9px solid transparent;\n}\n\n\/* Progress bar *\/\n.skycam-embed .sky2-progress-bar {\n  width: 100%;\n  height: 4px;\n  background: rgba(255,255,255,0.3);\n  border-radius: 2px; cursor: pointer; position: relative; overflow: hidden;\n}\n.skycam-embed .sky2-progress-bar:hover { height: 6px; }\n.skycam-embed .sky2-progress-fill {\n  height: 100%; background: rgba(255,255,255,0.9);\n  width: 0%; border-radius: 2px; transition: width 0.1s linear;\n}\n<\/style>\n\n<script>\ndocument.addEventListener('DOMContentLoaded', () => {\n  document.querySelectorAll('.skycam-embed').forEach(initSkycamEmbed);\n\n  function initSkycamEmbed(root) {\n    \/\/ Aspect ratio from data-ratio=\"W:H\" or \"1.7778\"\n    const ratioAttr = (root.getAttribute('data-ratio') || '16:9').trim();\n    const aspect = parseRatio(ratioAttr);\n    if (aspect) root.style.setProperty('--sky2-aspect', aspect);\n\n    const video = root.querySelector('.sky2-video');\n    const source = video.querySelector('source');\n    const posterLayer = root.querySelector('.sky2-poster');\n    const overlay = root.querySelector('.sky2-overlay');\n    const dtEl = root.querySelector('.sky2-datetime');\n\n    const btnPlayPause = root.querySelector('.sky2-playpause');\n    const btnRewind = root.querySelector('.sky2-rewind');\n    const playIcon = btnPlayPause.querySelector('.sky2-play-icon');\n    const pauseIcon = btnPlayPause.querySelector('.sky2-pause-icon');\n    const bar = root.querySelector('.sky2-progress-bar');\n    const fill = root.querySelector('.sky2-progress-fill');\n\n    \/\/ Per-instance config\n    const dataVideo   = root.getAttribute('data-video');\n    const dataPoster  = root.getAttribute('data-poster');\n    const dataAutoplay = (root.getAttribute('data-autoplay') || 'false').toLowerCase() === 'true';\n    const delayMs     = parseInt(root.getAttribute('data-delay') || '2000', 10);\n\n    \/\/ Apply sources (with cache-bust)\n    let videoUrl  = dataVideo || (source && source.getAttribute('src')) || '';\n    let posterUrl = dataPoster || video.getAttribute('poster') || '';\n\n    if (dataPoster) {\n      video.setAttribute('poster', dataPoster);\n      const img = posterLayer.querySelector('img');\n      if (img) img.src = dataPoster;\n    }\n    if (videoUrl) {\n      source.setAttribute('src', cacheBust(videoUrl));\n      video.load();\n    }\n    posterLayer.style.display = 'block';\n\n    \/\/ Autoplay (optional). Default false to avoid conflicts with another hero.\n    if (dataAutoplay) setTimeout(() => tryPlay(), delayMs);\n    else showPaused();\n\n    \/\/ Controls\n    btnPlayPause.addEventListener('click', () => {\n      if (video.paused) {\n        if (video.ended) video.currentTime = 0;\n        tryPlay();\n      } else {\n        video.pause();\n      }\n    });\n\n    btnRewind.addEventListener('click', () => {\n      video.currentTime = Math.max(0, video.currentTime - 2);\n      if (video.paused && dataAutoplay) tryPlay();\n    });\n\n    \/\/ Video events\n    video.addEventListener('play', () => {\n      playIcon.style.display = 'none';\n      pauseIcon.style.display = 'flex';\n      overlay.classList.remove('video-paused');\n      posterLayer.style.display = 'none';\n    });\n    video.addEventListener('pause', showPaused);\n    video.addEventListener('ended', () => {\n      showPaused();\n      posterLayer.style.display = 'block';\n    });\n\n    \/\/ Progress\n    video.addEventListener('timeupdate', () => {\n      if (video.duration) {\n        fill.style.width = (video.currentTime \/ video.duration) * 100 + '%';\n      }\n    });\n\n    \/\/ Seek\n    bar.addEventListener('click', (e) => {\n      const rect = bar.getBoundingClientRect();\n      const pos = (e.clientX - rect.left) \/ rect.width;\n      if (video.duration) video.currentTime = pos * video.duration;\n    });\n    bar.addEventListener('touchstart', (e) => {\n      e.preventDefault();\n      const t = e.touches[0];\n      const rect = bar.getBoundingClientRect();\n      const pos = (t.clientX - rect.left) \/ rect.width;\n      if (video.duration) video.currentTime = pos * video.duration;\n    });\n\n    \/\/ Timestamp: try video HEAD, then poster HEAD\n    setTimestamp(dtEl, videoUrl, posterUrl);\n\n    \/\/ Helpers\n    function cacheBust(url) {\n      const sep = url.includes('?') ? '&' : '?';\n      return url + sep + 't=' + Date.now();\n    }\n    function tryPlay() {\n      video.play().then(() => {\n        playIcon.style.display = 'none';\n        pauseIcon.style.display = 'flex';\n        overlay.classList.remove('video-paused');\n        posterLayer.style.display = 'none';\n      }).catch(() => { showPaused(); });\n    }\n    function showPaused() {\n      playIcon.style.display = 'block';\n      pauseIcon.style.display = 'none';\n      overlay.classList.add('video-paused');\n    }\n    function setTimestamp(el, vUrl, pUrl) {\n      const fmt = new Intl.DateTimeFormat('en-US', {\n        weekday: 'long', year: 'numeric', month: 'long', day: 'numeric',\n        hour: '2-digit', minute: '2-digit', timeZoneName: 'short'\n      });\n      const head = (url) =>\n        fetch(url, { method: 'HEAD' }).then(r => ({ ok: r.ok, lm: r.headers.get('Last-Modified') }))\n                                     .catch(() => ({ ok: false, lm: null }));\n      (async () => {\n        let lm = null;\n        if (vUrl) {\n          const r1 = await head(vUrl);\n          if (r1.ok && r1.lm) lm = r1.lm;\n        }\n        if (!lm && pUrl) {\n          const r2 = await head(pUrl);\n          if (r2.ok && r2.lm) lm = r2.lm;\n        }\n        el.textContent = lm ? ('Video captured: ' + fmt.format(new Date(lm)))\n                            : 'Video timestamp unavailable';\n      })();\n    }\n    function parseRatio(r) {\n      if (!r) return null;\n      if (r.includes(':')) {\n        const [w, h] = r.split(':').map(Number);\n        if (w > 0 && h > 0) return `${w}\/${h}`;\n      } else {\n        const f = parseFloat(r);\n        if (f > 0) return `${f}\/1`;\n      }\n      return null;\n    }\n  }\n});\n<\/script>\n\n\n\n<p>The 24-hour skycam time-lapse above shows conditions across day and night. We use it to compare with our telescope captures to confirm whether any irregularities in our astrophotographs were caused by clouds, light, or real celestial events. Daytime footage also documents solar observations and serves as a visual record of daily sky activity.<\/p>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-group alignfull has-background has-global-padding is-layout-constrained wp-block-group-is-layout-constrained\" style=\"background-color:#e5e9ec;padding-top:var(--wp--preset--spacing--small);padding-bottom:var(--wp--preset--spacing--small)\">\n<div class=\"wp-block-columns are-vertically-aligned-top is-layout-flex wp-container-core-columns-is-layout-7db7aa3e wp-block-columns-is-layout-flex\" style=\"padding-right:0;padding-left:0\">\n<div class=\"wp-block-column is-vertically-aligned-top is-layout-flow wp-block-column-is-layout-flow\">\n<h2 class=\"wp-block-heading has-medium-font-size\" style=\"text-transform:uppercase\">Current Star trails<\/h2>\n\n\n\n<img decoding=\"async\" src=\"https:\/\/spacehaven.space\/spaces\/allsky\/startrails\/startrails.jpg\" alt=\"Star Trails\" style=\"width: 100%; height: auto;\">\n<h4 style=\"text-align: center; margin-top: 8px; font-family: 'Onest', sans-serif;\">Last Night&#8217;s Star Trails<\/h4>\n<script>\ndocument.currentScript.previousElementSibling.previousElementSibling.src += '?t=' + new Date().getTime();\n<\/script>\n\n\n\n<p>Please note that on moonlit nights, the Star Trails image may appear fully illuminated with no visible stars. Additionally, extremely bright events can impact the image quality.<\/p>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-vertically-aligned-top is-layout-flow wp-block-column-is-layout-flow\">\n<h2 class=\"wp-block-heading has-medium-font-size\" style=\"text-transform:uppercase\">Star TRAILS<\/h2>\n\n\n\n<img decoding=\"async\" src=\"https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/startrails-20251016.jpg\" alt=\"Star Trails\" style=\"width: 100%; height: auto;\">\n<h4 style=\"text-align: center; margin-top: 8px; font-family: 'Onest', sans-serif;\">Example of a clear night&#8217;s Startrails image<\/h4>\n\n\n\n<p>A Star Trails image is created by stacking multiple photos taken throughout a single night to capture the movement of stars across the sky. This technique provides a clear visual representation of star paths, allowing us to quickly assess the clarity of the night and evaluate the astrophotography results from that session.<\/p>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-group alignfull has-global-padding is-layout-constrained wp-block-group-is-layout-constrained\" style=\"padding-top:var(--wp--preset--spacing--small);padding-bottom:var(--wp--preset--spacing--small)\">\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-7db7aa3e wp-block-columns-is-layout-flex\" style=\"padding-right:0;padding-left:0\">\n<div class=\"wp-block-column is-vertically-aligned-center is-layout-flow wp-block-column-is-layout-flow\">\n<h2 class=\"wp-block-heading has-medium-font-size\" style=\"text-transform:uppercase\">Keograms<\/h2>\n\n\n\n<p>Below is the actual Keogram from last night. Please note that it also includes daytime data. <strong>So dusk is in the middle of the Keogram.<\/strong> <\/p>\n\n\n\n<img decoding=\"async\" src=\"https:\/\/spacehaven.space\/spaces\/allsky\/keograms\/keogram.jpg\" alt=\"Keogram\" style=\"width: 100%; height: auto;\">\n<h4 style=\"text-align: center; margin-top: 8px; font-family: 'Onest', sans-serif;\">Last Night&#8217;s Keogram<\/h4>\n<script>\ndocument.currentScript.previousElementSibling.previousElementSibling.src += '?t=' + new Date().getTime();\n<\/script>\n\n\n\n<h3 class=\"wp-block-heading\">Explanation:<\/h3>\n\n\n\n<p>A Keogram\u2014a single image that provides a concise summary of a night\u2019s sky activity. It allows us to quickly assess the quality of our astrophotographs from that night. When we encounter quality issues or unexpected brightness in our frames, the Keogram helps us identify the source, enabling us to correct the problem or discard affected frames as needed.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"341\" src=\"https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/Screenshot-2025-10-08-at-21.24.33-1024x341.png\" alt=\"\" class=\"wp-image-7306\" srcset=\"https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/Screenshot-2025-10-08-at-21.24.33-1024x341.png 1024w, https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/Screenshot-2025-10-08-at-21.24.33-300x100.png 300w, https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/Screenshot-2025-10-08-at-21.24.33-2000x667.png 2000w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>A central vertical column, 1 pixel wide, is extracted from each image captured by the SkyCam and added to the keogram from left to right. Keograms display only the activity at the center of the image and do not show events occurring to the East or West.<\/p>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-group alignfull has-background has-global-padding is-layout-constrained wp-block-group-is-layout-constrained\" style=\"background-color:#e5e9ec;padding-top:var(--wp--preset--spacing--small);padding-bottom:var(--wp--preset--spacing--small)\">\n<div class=\"wp-block-columns are-vertically-aligned-top is-layout-flex wp-container-core-columns-is-layout-7db7aa3e wp-block-columns-is-layout-flex\" style=\"padding-right:0;padding-left:0\">\n<div class=\"wp-block-column is-vertically-aligned-top is-layout-flow wp-block-column-is-layout-flow\">\n<h2 class=\"wp-block-heading has-medium-font-size\" style=\"text-transform:uppercase\">SkyCam system design<\/h2>\n\n\n\n<p>The SkyCam system features specially designed components, centered around a ZWO ASI224MC camera housed in a protective enclosure mounted atop the Space Haven Observatory. Fitted with a 2.5mm all-sky lens, it provides a 170\u00b0 panoramic view of the sky. The camera interfaces with a Raspberry Pi 4 computer situated inside the Observatory.<\/p>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-vertically-aligned-top is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:33%\">\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"768\" src=\"https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/IMG_5699-1024x768.jpeg\" alt=\"To show where the SkyCam camera is installed\" class=\"wp-image-7316\" srcset=\"https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/IMG_5699-1024x768.jpeg 1024w, https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/IMG_5699-300x225.jpeg 300w, https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/IMG_5699-2000x1500.jpeg 2000w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\"><strong>SkyCam<\/strong><\/figcaption><\/figure>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-group alignfull has-background has-global-padding is-layout-constrained wp-block-group-is-layout-constrained\" style=\"background-color:#e5e9ec;padding-top:var(--wp--preset--spacing--small);padding-bottom:var(--wp--preset--spacing--small)\">\n<div class=\"wp-block-columns are-vertically-aligned-top is-layout-flex wp-container-core-columns-is-layout-7db7aa3e wp-block-columns-is-layout-flex\" style=\"padding-right:0;padding-left:0\">\n<div class=\"wp-block-column is-vertically-aligned-top is-layout-flow wp-block-column-is-layout-flow\" style=\"flex-basis:33%\">\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"753\" height=\"1024\" src=\"https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/IMG_5700-753x1024.jpeg\" alt=\"RPi for SkCam\" class=\"wp-image-7317\" style=\"aspect-ratio:1;object-fit:cover\" srcset=\"https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/IMG_5700-753x1024.jpeg 753w, https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/IMG_5700-221x300.jpeg 221w, https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/IMG_5700-2000x2720.jpeg 2000w, https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/IMG_5700-scaled.jpeg 1882w\" sizes=\"auto, (max-width: 753px) 100vw, 753px\" \/><figcaption class=\"wp-element-caption\"><strong>SkyCam Raspberry Pi4 <\/strong><\/figcaption><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-vertically-aligned-top is-layout-flow wp-block-column-is-layout-flow\">\n<h2 class=\"wp-block-heading has-medium-font-size\" style=\"text-transform:uppercase\">SkyCam system design (continued)<\/h2>\n\n\n\n<p>The system continuously records single frames of the sky 24 hours a day and every morning process those images to produce the full motion movie, Startrails and Keogram that you see above.  The software that controls and runs the SkyCam system is from the excellent ALLSKY implementation that can be found on Github <a href=\"https:\/\/github.com\/AllskyTeam\/allsky\">here<\/a>.  <\/p>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>SkyCam Live View 24-hour time-lapse of the sky above Space Haven Observatory SKycam The SkyCam recording above is a live 24-hour time-lapse of the sky above the Space Haven Observatory. It updates daily and, depending on conditions, you may witness a variety of events such as sunrise and sunset, lunar phases, star and planet movements, &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/spacehaven.space\/spaces\/skycam-v2-0\/\" class=\"more-link\">Read more<span class=\"screen-reader-text\"> &#8220;SkyCam-v2.0&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"page-templates\/full-width-transparent.php","meta":{"inspiro_hide_title":false,"footnotes":""},"class_list":["post-7734","page","type-page","status-publish","hentry"],"featured_media_urls":[],"_links":{"self":[{"href":"https:\/\/spacehaven.space\/spaces\/wp-json\/wp\/v2\/pages\/7734","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/spacehaven.space\/spaces\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/spacehaven.space\/spaces\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/spacehaven.space\/spaces\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/spacehaven.space\/spaces\/wp-json\/wp\/v2\/comments?post=7734"}],"version-history":[{"count":18,"href":"https:\/\/spacehaven.space\/spaces\/wp-json\/wp\/v2\/pages\/7734\/revisions"}],"predecessor-version":[{"id":7887,"href":"https:\/\/spacehaven.space\/spaces\/wp-json\/wp\/v2\/pages\/7734\/revisions\/7887"}],"wp:attachment":[{"href":"https:\/\/spacehaven.space\/spaces\/wp-json\/wp\/v2\/media?parent=7734"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}