{"id":7871,"date":"2025-11-02T00:10:21","date_gmt":"2025-11-02T04:10:21","guid":{"rendered":"https:\/\/spacehaven.space\/spaces\/?page_id=7871"},"modified":"2026-01-07T23:32:11","modified_gmt":"2026-01-08T04:32:11","slug":"observatory-dash","status":"publish","type":"page","link":"https:\/\/spacehaven.space\/spaces\/observatory-dash\/","title":{"rendered":"Observatory Dash"},"content":{"rendered":"\n<div class=\"wp-block-group alignfull has-foreground-background-color has-background has-global-padding is-layout-constrained wp-block-group-is-layout-constrained\">\n<div style=\"height:30px\" aria-hidden=\"true\" class=\"wp-block-spacer\"><\/div>\n\n\n\n<!-- =======================================================================================\nINSTRUCTIONS \u2014 HOW TO USE \/ TUNE\n1) Paste this entire block into a WordPress \u201cCustom HTML\u201d block on a blank page.\n2) To change row heights, edit the CSS variables at the very top:\n     --row1-h: 140px;   \/* title + compact weather *\/\n     --row2-h: 380px;   \/* live HLS + interactive Astrospheric *\/\n     --row3-h: 380px;   \/* live SkyCam + timelapse *\/\n   You can use px, vh, etc.\n3) Width behavior:\n   - The grid is 2 columns on desktop, 1 column under 900px.\n4) Scripts:\n   - hls.js enables HLS playback on Chrome\/Firefox\/Edge.\n   - Astrospheric embed JS powers the interactive panel.\n5) Everything is fully scoped to 'shdash-*' classes to avoid conflicts.\n======================================================================================= -->\n\n<style>\n  \/* ======= GLOBAL TUNING (edit these) ======= *\/\n  .shdash-wrap{\n    --gap:16px; --radius:12px; --pad:10px;\n    --row1-h: 140px;   \/* change me *\/\n    --row2-h: 380px;   \/* change me *\/\n    --row3-h: 380px;   \/* change me *\/\n    font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;\n  }\n\n  \/* ======= GRID & CARD ======= *\/\n  .shdash-grid{ display:grid; gap:var(--gap); grid-template-columns: repeat(2, minmax(0,1fr)); }\n  @media (max-width:900px){ .shdash-grid{ grid-template-columns: 1fr; } }\n\n  .shdash-card{\n    background:#0b0b0c; border:1px solid #222; border-radius:var(--radius);\n    box-shadow: 0 1px 6px rgba(0,0,0,.35); color:#eaeaea; overflow:hidden;\n  }\n  .shdash-head{\n    padding:8px 12px; font-size:.95rem; letter-spacing:.02em;\n    border-bottom:1px solid #1b1b1b; background:linear-gradient(180deg,#121212,#0d0d0d);\n  }\n  .shdash-body{ padding:var(--pad); }\n\n  \/* Heights by row *\/\n  .r1 .shdash-media{ height: var(--row1-h); }\n  .r2 .shdash-media{ height: var(--row2-h); }\n  .r3 .shdash-media{ height: var(--row3-h); }\n\n  \/* Media container *\/\n  .shdash-media{\n    position:relative; width:100%; overflow:hidden; background:#000; border-radius:10px;\n    display:flex; align-items:center; justify-content:center;\n  }\n  .shdash-media > img, .shdash-media > video{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }\n\n \/* Title card *\/\n.shdash-title{\n  width:100%;\n  height:100%;\n  display:flex;\n  align-items:center;\n  justify-content:center;\n  color:#fff;\n  font-weight:600;\n  font-size: clamp(1.8rem, 2.4vw, 3.0rem); \/* roughly H2\u2013H3 scale *\/\n  letter-spacing:.02em;\n}\n\n\n  \/* Live video *\/\n  .shdash-video{ width:100%; height:100%; background:#000; border:0; display:block; border-radius:10px; }\n\n  \/* SkyCam live image + caption *\/\n  .shdash-caption{ margin-top:6px; text-align:center; font-size:.9rem; opacity:.9; }\n  .shdash-sub{ margin:2px 0 0; text-align:center; font-size:.78rem; opacity:.85; }\n\n  \/* Astrospheric compact banner + interactive panel *\/\n  .shdash-astro-note{ text-align:center; font-size:.8rem; opacity:.8; margin-top:6px; }\n  .shdash-astro-panel{ width:100%; max-width:900px; max-height: calc(var(--row2-h) - 60px); overflow:auto; border:1px solid #222; border-radius:10px; background:#0b0b0c; }\n\n  \/* Timelapse (compact controls) *\/\n  .shdash-tl .sky2-overlay{\n    position:absolute; inset:auto 8px 8px auto; background:rgba(0,0,0,.45); padding:6px 8px; border-radius:8px; z-index:3;\n  }\n  .shdash-tl .sky2-datetime{ margin:0; font-size:.8rem; color:#fff; }\n  .shdash-tl .sky2-controls-wrap{ position:absolute; right:8px; bottom:8px; z-index:3; display:flex; gap:8px; align-items:center; }\n  .shdash-tl .sky2-btn{ background:rgba(255,255,255,.9); border:none; border-radius:8px; width:38px; height:38px; cursor:pointer; }\n  .shdash-tl .sky2-progress-bar{ position:absolute; left:8px; right:8px; bottom:8px; height:4px; background:rgba(255,255,255,.35); border-radius:3px; }\n  .shdash-tl .sky2-progress-fill{ height:100%; width:0%; background:#fff; }\n<\/style>\n\n<div class=\"shdash-wrap\">\n  <!-- =================== ROW 1 =================== -->\n  <div class=\"shdash-grid r1\">\n    <!-- CARD A \u2014 Title -->\n    <!-- NAME: Title \/ \"Observatory Dash\" -->\n    <section class=\"shdash-card\">\n      <div class=\"shdash-head\">Dashboard<\/div>\n      <div class=\"shdash-body\">\n        <div class=\"shdash-media\">\n          <div class=\"shdash-title\">Observatory Dash<\/div>\n        <\/div>\n      <\/div>\n    <\/section>\n\n    <!-- CARD B \u2014 WEATHER (compact signature + link) -->\n    <!-- NAME: Weather Signature (compact) -->\n    <section class=\"shdash-card\">\n      <div class=\"shdash-head\">Current Conditions \u2014 Compact<\/div>\n      <div class=\"shdash-body\">\n        <div class=\"shdash-media\" style=\"position:relative;\">\n          <a target=\"_blank\" rel=\"noopener\"\n             href=\"https:\/\/www.astrospheric.com\/?Latitude=40.365386962890625&#038;Longitude=-74.03687719571066&#038;src=sig\"\n             style=\"position:absolute; inset:0; display:flex; align-items:center; justify-content:center;\">\n            <img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/astrodatabot.azurewebsites.net\/api\/v1\/sig.gif?code=DO1YwhxV4phITXWnJmpWWg3rKObzX\/6vJlVVZ1stNNMcsTnbk5rF4Q==&#038;UID=50624&#038;Latitude=40.365386962890625&#038;Longitude=-74.03687719571066\"\n                 alt=\"Astrospheric Signature\" width=\"368\" height=\"100\"\n                 style=\"border:1px solid #333;\">\n          <\/a>\n        <\/div>\n      <\/div>\n    <\/section>\n  <\/div>\n\n  <!-- =================== ROW 2 =================== -->\n  <div class=\"shdash-grid r2\">\n    <!-- CARD C \u2014 Live HLS Stream -->\n    <!-- NAME: Live HLS Stream (m3u8) -->\n    <section class=\"shdash-card\">\n      <div class=\"shdash-head\">Live Observatory Stream<\/div>\n      <div class=\"shdash-body\">\n        <div class=\"shdash-media\">\n          <video id=\"shdash-live\" class=\"shdash-video\" controls autoplay muted playsinline><\/video>\n        <\/div>\n      <\/div>\n    <\/section>\n\n    <!-- CARD D \u2014 Astrospheric Interactive Embed -->\n    <!-- NAME: Astrospheric Interactive Panel -->\n    <section class=\"shdash-card\">\n      <div class=\"shdash-head\">Forecast \u2014 Clouds, Transparency, Seeing (Interactive scroll)<\/div>\n      <div class=\"shdash-body\">\n        <div class=\"shdash-media\" style=\"display:flex; align-items:center; justify-content:center;\">\n          <div id=\"AstrosphericEmbedContainer\" class=\"shdash-astro-panel\"><\/div>\n        <\/div>\n      <\/div>\n    <\/section>\n  <\/div>\n\n  <!-- =================== ROW 3 =================== -->\n  <div class=\"shdash-grid r3\">\n    <!-- CARD E \u2014 SkyCam Live image + timestamp -->\n    <!-- NAME: SkyCam Live Image + Timestamp -->\n    <section class=\"shdash-card\">\n      <div class=\"shdash-head\">SkyCam \u2014 Live Image<\/div>\n      <div class=\"shdash-body\">\n        <div class=\"shdash-media\">\n          <img decoding=\"async\" id=\"skycam-live\" src=\"https:\/\/spacehaven.space\/spaces\/allsky\/image.jpg\" alt=\"SkyCam Live\">\n        <\/div>\n        <div class=\"shdash-caption\">SkyCam Live Image<\/div>\n        <p id=\"skycam-timestamp\" class=\"shdash-sub\"><\/p>\n      <\/div>\n    <\/section>\n\n    <!-- CARD F \u2014 Yesterday Timelapse -->\n    <!-- NAME: Yesterday\u2019s Timelapse (scoped player) -->\n    <section class=\"shdash-card shdash-tl\">\n      <div class=\"shdash-head\">Yesterday\u2019s Time-lapse<\/div>\n      <div class=\"shdash-body\">\n        <div class=\"shdash-media\">\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=\"16:9\"\n               style=\"position:absolute; inset:0;\">\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            <div class=\"sky2-poster\"><img decoding=\"async\" src=\"https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/startrails-20251016.jpg\" alt=\"\"><\/div>\n            <div class=\"sky2-overlay\"><p class=\"sky2-datetime\">Loading video timestamp\u2026<\/p><\/div>\n            <div class=\"sky2-controls-wrap\">\n              <button class=\"sky2-btn sky2-rewind\" aria-label=\"Rewind 2 seconds\" title=\"Rewind 2 seconds\">\u27f2<\/button>\n              <button class=\"sky2-btn sky2-playpause\" aria-label=\"Play\/Pause\">\u23ef<\/button>\n            <\/div>\n            <div class=\"sky2-progress-bar\"><div class=\"sky2-progress-fill\"><\/div><\/div>\n          <\/div>\n        <\/div>\n      <\/div>\n    <\/section>\n  <\/div>\n<\/div>\n\n<!-- ===== Dependencies ===== -->\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/hls.js@latest\"><\/script>\n<script src=\"https:\/\/astrosphericcloudstorage.blob.core.windows.net\/embed\/astrosphericembed.js\"><\/script>\n\n<script>\n(function(){\n  \/* ========= Live HLS Stream ========= *\/\n  <!-- ============================================================ SPACE HAVEN OBSERVATORY \u2014 TEMPORARY PUBLIC STREAM LINK SETUP ============================================================ 1) Open a terminal on your Raspberry Pi. 2) Run this command to start a temporary Cloudflare Quick Tunnel: cloudflared tunnel --url http:\/\/localhost:8080 \u2022 This connects your Pi\u2019s local HLS server (nginx on port 8080) to Cloudflare\u2019s public edge. \u2022 It prints a public URL such as: https:\/\/random-name.trycloudflare.com 3) Your live HLS stream will be available at: https:\/\/random-name.trycloudflare.com\/observatory\/index.m3u8 \u2022 Replace \"random-name\" with the exact subdomain shown by Cloudflare. \u2022 The stream remains accessible while this terminal session is open. Closing the terminal or rebooting the Pi will end the tunnel. 4) To create a new public link later, simply repeat Step 2. ---------------------------------------------------------------- Notes: \u2022 Each tunnel generates a new unique URL (non-persistent). \u2022 For a permanent link such as https:\/\/video.spacehaven.space, you\u2019ll later set up a \u201cNamed Tunnel\u201d with Cloudflare. UPDATE IN SCRIPT SECTION SCROLL DOWN FURTHER ---------------------------------------------------------------- -->\n  const streamURL = \"https:\/\/stands-southwest-domain-stereo.trycloudflare.com\/observatory\/index.m3u8\"; \/\/ \u2705 HLS URL\n  const vid = document.getElementById(\"shdash-live\");\n  if (vid) {\n    if (vid.canPlayType(\"application\/vnd.apple.mpegurl\")) {\n      vid.src = streamURL;       \/\/ Safari \/ iOS\n    } else if (window.Hls && Hls.isSupported()) {\n      const hls = new Hls();\n      hls.loadSource(streamURL); \/\/ Chrome\/Firefox\/Edge\n      hls.attachMedia(vid);\n    } else {\n      vid.outerHTML = \"<p style='padding:8px;'>This browser cannot play HLS.<\/p>\";\n    }\n  }\n\n  \/* ========= Astrospheric Interactive ========= *\/\n  if (window.m_AstrosphericEmbed && document.getElementById(\"AstrosphericEmbedContainer\")) {\n    m_AstrosphericEmbed.Create(\"AstrosphericEmbedContainer\", 40.368089, -74.038527);\n  }\n\n  \/* ========= SkyCam Live image + timestamp ========= *\/\n  function updateSkyCam(){\n    const img = document.getElementById('skycam-live');\n    const ts  = document.getElementById('skycam-timestamp');\n    const base = 'https:\/\/spacehaven.space\/spaces\/allsky\/image.jpg';\n    if (img){ img.src = base + '?t=' + Date.now(); }\n    if (ts){\n      const now = new Date();\n      ts.textContent = now.toLocaleString('en-US', {\n        year:'numeric', month:'long', day:'numeric',\n        hour:'2-digit', minute:'2-digit', second:'2-digit',\n        timeZoneName:'short'\n      }) + ' \u2014 USA';\n    }\n  }\n  updateSkyCam();\n  setInterval(updateSkyCam, 60000);\n\n  \/* ========= Timelapse player (scoped) ========= *\/\n  document.querySelectorAll('.skycam-embed').forEach(initSkycamEmbed);\n  function initSkycamEmbed(root){\n    \/\/ Size is controlled by its parent .shdash-media, so we only attach logic.\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    const btnPlayPause = root.querySelector('.sky2-playpause');\n    const btnRewind = root.querySelector('.sky2-rewind');\n    const bar = root.querySelector('.sky2-progress-bar');\n    const fill = root.querySelector('.sky2-progress-fill');\n\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    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 && posterLayer.querySelector('img'); if (img) img.src = dataPoster;\n    }\n    if (videoUrl) { source.setAttribute('src', cacheBust(videoUrl)); video.load(); }\n    if (posterLayer) posterLayer.style.display = 'block';\n\n    if (dataAutoplay) setTimeout(()=>tryPlay(), delayMs);\n\n    btnPlayPause && btnPlayPause.addEventListener('click', ()=>{ video.paused ? tryPlay() : video.pause(); });\n    btnRewind && btnRewind.addEventListener('click', ()=>{ video.currentTime = Math.max(0, video.currentTime - 2); });\n\n    video.addEventListener('play', ()=>{ overlay && overlay.classList.remove('video-paused'); if (posterLayer) posterLayer.style.display='none'; });\n    video.addEventListener('pause', ()=>{ overlay && overlay.classList.add('video-paused'); });\n    video.addEventListener('ended', ()=>{ overlay && overlay.classList.add('video-paused'); if (posterLayer) posterLayer.style.display='block'; });\n    video.addEventListener('timeupdate', ()=>{ if (video.duration && fill) fill.style.width = (video.currentTime \/ video.duration) * 100 + '%'; });\n\n    bar && bar.addEventListener('click', (e)=>{\n      const rect = bar.getBoundingClientRect(); const pos = (e.clientX - rect.left) \/ rect.width;\n      if (video.duration) video.currentTime = pos * video.duration;\n    });\n\n    setTimestamp(dtEl, videoUrl, posterUrl);\n\n    function cacheBust(url){ const sep = url.includes('?') ? '&' : '?'; return url + sep + 't=' + Date.now(); }\n    function tryPlay(){ video.play().catch(()=>{}); }\n    async function setTimestamp(el, vUrl, pUrl){\n      if (!el) return;\n      const fmt = new Intl.DateTimeFormat('en-US',{weekday:'long',year:'numeric',month:'long',day:'numeric',hour:'2-digit',minute:'2-digit',timeZoneName:'short'});\n      async function head(url){ try{ const r = await fetch(url,{method:'HEAD'}); return r.ok ? r.headers.get('Last-Modified') : null; }catch(e){ return null; } }\n      let lm = vUrl ? await head(vUrl) : null; if (!lm && pUrl) lm = await head(pUrl);\n      el.textContent = lm ? ('Captured: ' + fmt.format(new Date(lm))) : 'Timestamp unavailable';\n    }\n  }\n})();\n<\/script>\n<\/div>\n\n\n\n<p class=\"has-white-color has-text-color has-link-color wp-elements-9d96eb2ab3c6dfc235b06413497539b7\"><a href=\"http:\/\/192.168.0.142:8080\/quick\/\">Quick<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Dashboard Observatory Dash Current Conditions \u2014 Compact Live Observatory Stream Forecast \u2014 Clouds, Transparency, Seeing (Interactive scroll) SkyCam \u2014 Live Image SkyCam Live Image Yesterday\u2019s Time-lapse Loading video timestamp\u2026 \u27f2 \u23ef Quick<\/p>\n","protected":false},"author":1,"featured_media":7540,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"page-templates\/no-title.php","meta":{"inspiro_hide_title":false,"footnotes":""},"class_list":["post-7871","page","type-page","status-publish","has-post-thumbnail","hentry"],"featured_media_urls":{"thumbnail":["https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/34421867-150x150.jpeg",150,150,true],"medium":["https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/34421867-300x169.jpeg",300,169,true],"medium_large":["https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/34421867.jpeg",768,432,false],"large":["https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/34421867-1024x576.jpeg",950,534,true],"1536x1536":["https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/34421867-1536x864.jpeg",1536,864,true],"2048x2048":["https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/34421867.jpeg",2000,1125,false],"ultp_layout_landscape_large":["https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/34421867-1200x800.jpeg",1200,800,true],"ultp_layout_landscape":["https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/34421867-870x570.jpeg",870,570,true],"ultp_layout_portrait":["https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/34421867-600x900.jpeg",600,900,true],"ultp_layout_square":["https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/34421867-600x600.jpeg",600,600,true],"inspiro-featured-image":["https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/34421867.jpeg",2000,1125,false],"inspiro-loop":["https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/34421867-950x320.jpeg",950,320,true],"inspiro-loop@2x":["https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/34421867-1900x640.jpeg",1900,640,true],"portfolio_item-thumbnail":["https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/34421867-600x400.jpeg",600,400,true],"portfolio_item-thumbnail@2x":["https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/34421867-1200x800.jpeg",1200,800,true],"portfolio_item-masonry":["https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/34421867-600x338.jpeg",600,338,true],"portfolio_item-masonry@2x":["https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/34421867-1200x675.jpeg",1200,675,true],"portfolio_item-thumbnail_cinema":["https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/34421867-800x335.jpeg",800,335,true],"portfolio_item-thumbnail_portrait":["https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/34421867-600x900.jpeg",600,900,true],"portfolio_item-thumbnail_portrait@2x":["https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/34421867-1200x1125.jpeg",1200,1125,true],"portfolio_item-thumbnail_square":["https:\/\/spacehaven.space\/spaces\/wp-content\/uploads\/2025\/10\/34421867-800x800.jpeg",800,800,true]},"_links":{"self":[{"href":"https:\/\/spacehaven.space\/spaces\/wp-json\/wp\/v2\/pages\/7871","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=7871"}],"version-history":[{"count":42,"href":"https:\/\/spacehaven.space\/spaces\/wp-json\/wp\/v2\/pages\/7871\/revisions"}],"predecessor-version":[{"id":8378,"href":"https:\/\/spacehaven.space\/spaces\/wp-json\/wp\/v2\/pages\/7871\/revisions\/8378"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/spacehaven.space\/spaces\/wp-json\/wp\/v2\/media\/7540"}],"wp:attachment":[{"href":"https:\/\/spacehaven.space\/spaces\/wp-json\/wp\/v2\/media?parent=7871"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}