From 52e2ffc06c57324b51ced7213488fa0b2d26c3ee Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Sun, 8 Feb 2026 10:36:03 -0500 Subject: [PATCH] Redesign countdown overlay as compact frosted-glass bottom bar Countdown preview now anchors to bottom of media container instead of covering the entire video. Uses backdrop-filter blur with theme-aware semi-transparent background (light/dark). Compact single-row grid layout with 60px thumb, title+number, and action buttons inline. Also completes the stalled watch mode v2 changes: static content timer, autoplay toggle persistence, audio container wrapping in hardSwap, and watch_json endpoint accepting non-playable media types. --- make_post_sell/GIT_HASH | 2 +- make_post_sell/static/js/watch.js | 178 ++++++++++++++++------------ make_post_sell/templates/content.j2 | 49 +++++--- make_post_sell/templates/product.j2 | 49 +++++--- make_post_sell/views/watch.py | 6 +- 5 files changed, 170 insertions(+), 114 deletions(-) diff --git a/make_post_sell/GIT_HASH b/make_post_sell/GIT_HASH index 27c6057..aba50a9 100644 --- a/make_post_sell/GIT_HASH +++ b/make_post_sell/GIT_HASH @@ -1 +1 @@ -1b704f5 \ No newline at end of file +d03178b \ No newline at end of file diff --git a/make_post_sell/static/js/watch.js b/make_post_sell/static/js/watch.js index b98c5e6..78c9c3c 100644 --- a/make_post_sell/static/js/watch.js +++ b/make_post_sell/static/js/watch.js @@ -1,7 +1,6 @@ (function() { - // Find the active media element (video or audio) + // Find the active media element (video or audio), or null for static content var activeMedia = document.getElementById('watch-video') || document.getElementById('watch-audio'); - if (!activeMedia) return; // SPA state var standbyMedia = null; @@ -9,9 +8,35 @@ var queue = []; var countdownTimer = null; var crossfadeTimer = null; + var staticTimer = null; var COUNTDOWN_SECONDS = 14; var CROSSFADE_DURATION = 3000; var CROSSFADE_STEPS = 60; + var STATIC_DISPLAY_SECONDS = 42; + + // Autoplay preference (persisted in localStorage) + var autoplayEnabled = true; + try { + var saved = localStorage.getItem('watchAutoplay'); + if (saved !== null) autoplayEnabled = saved === '1'; + } catch (e) {} + + // Set up autoplay toggle + var autoplayToggle = document.getElementById('watch-autoplay-toggle'); + if (autoplayToggle) { + autoplayToggle.checked = autoplayEnabled; + autoplayToggle.addEventListener('change', function() { + autoplayEnabled = this.checked; + try { localStorage.setItem('watchAutoplay', autoplayEnabled ? '1' : '0'); } catch (e) {} + if (!autoplayEnabled) { + cancelCountdown(); + if (staticTimer) { clearTimeout(staticTimer); staticTimer = null; } + } else { + // If on a static page, restart the timer + if (!activeMedia) startStaticTimer(); + } + }); + } // Restore queue from sessionStorage try { @@ -19,15 +44,17 @@ if (savedQueue) queue = JSON.parse(savedQueue); } catch (e) {} - // --- Autoplay with sound (existing behavior) --- - var playPromise = activeMedia.play(); - if (playPromise !== undefined) { - playPromise.catch(function() { - activeMedia.muted = true; - activeMedia.play().then(function() { - showUnmuteOverlay(activeMedia); - }).catch(function() {}); - }); + // --- Autoplay with sound (for video/audio) --- + if (activeMedia) { + var playPromise = activeMedia.play(); + if (playPromise !== undefined) { + playPromise.catch(function() { + activeMedia.muted = true; + activeMedia.play().then(function() { + showUnmuteOverlay(activeMedia); + }).catch(function() {}); + }); + } } function showUnmuteOverlay(media) { @@ -40,8 +67,12 @@ }); } + // Determine current media type + var currentMediaType = activeMedia ? activeMedia.tagName.toLowerCase() : 'static'; + // --- Create standby media element --- function createStandbyMedia(type) { + if (type === 'static') return null; var el = document.createElement(type === 'audio' ? 'audio' : 'video'); el.preload = 'auto'; el.style.display = 'none'; @@ -50,9 +81,9 @@ return el; } - // Determine current media type from the active element - var currentMediaType = activeMedia.tagName.toLowerCase(); - standbyMedia = createStandbyMedia(currentMediaType); + if (activeMedia) { + standbyMedia = createStandbyMedia(currentMediaType); + } // --- Queue management --- function saveQueue() { @@ -95,7 +126,6 @@ titleSpan.textContent = item.title; titleSpan.style.cursor = 'pointer'; titleSpan.addEventListener('click', function() { - // Play this item immediately, remove from queue var playItem = queue.splice(i, 1)[0]; saveQueue(); renderQueue(); @@ -129,21 +159,31 @@ // --- SPA navigation --- function fetchAndNavigate(productId) { cancelCountdown(); + if (staticTimer) { clearTimeout(staticTimer); staticTimer = null; } fetchWatchData(productId).then(function(data) { transitionTo(data); }).catch(function() { - // Fallback: hard navigate - window.location.href = '/watch/' + productId + '/json'; + // Fallback: hard navigate to the canonical URL + // Try to find the link in the related items + var link = document.querySelector('[data-watch-id="' + productId + '"]'); + if (link) { + window.location.href = link.getAttribute('href'); + } }); } function transitionTo(data) { - var sameType = (data.media_type === currentMediaType); + var isPlayable = (data.media_type === 'video' || data.media_type === 'audio'); - if (sameType) { + if (isPlayable && activeMedia && data.media_type === currentMediaType) { crossfade(data); - } else { + } else if (isPlayable) { hardSwap(data); + } else { + // Static content (PDF, image, etc.) — hard navigate + if (data.canonical_url) { + window.location.href = data.canonical_url; + } } } @@ -151,8 +191,7 @@ function crossfade(data) { if (crossfadeTimer) clearInterval(crossfadeTimer); - // Ensure standby matches media type - if (standbyMedia.tagName.toLowerCase() !== data.media_type) { + if (!standbyMedia || standbyMedia.tagName.toLowerCase() !== data.media_type) { hardSwap(data); return; } @@ -164,7 +203,6 @@ var standbyPlayPromise = standbyMedia.play(); if (standbyPlayPromise !== undefined) { standbyPlayPromise.catch(function() { - // Crossfade failed, hard swap hardSwap(data); return; }); @@ -191,12 +229,10 @@ activeMedia.style.display = 'none'; standbyMedia.style.display = ''; - // Swap references var tmp = activeMedia; activeMedia = standbyMedia; standbyMedia = tmp; - // Apply correct attributes activeMedia.id = currentMediaType === 'video' ? 'watch-video' : 'watch-audio'; activeMedia.controls = true; activeMedia.className = currentMediaType === 'video' ? 'product-main' : ''; @@ -209,30 +245,29 @@ // --- Hard swap (different media types or fallback) --- function hardSwap(data) { - activeMedia.pause(); + if (activeMedia) activeMedia.pause(); var container = document.getElementById('watch-media-container'); if (!container) return; - // Remove old standby if (standbyMedia && standbyMedia.parentNode) { standbyMedia.parentNode.removeChild(standbyMedia); } - // Determine new media type var newType = data.media_type; currentMediaType = newType; if (newType === 'audio') { - // Replace with audio container container.innerHTML = ''; - // Show album art if available + var audioWrap = document.createElement('div'); + audioWrap.className = 'watch-audio-container'; + if (data.thumbnail_url) { var img = document.createElement('img'); img.src = data.thumbnail_url; img.className = 'product-main audio-cover'; - container.appendChild(img); + audioWrap.appendChild(img); } var audio = document.createElement('audio'); @@ -240,11 +275,11 @@ audio.src = data.media_url; audio.autoplay = true; audio.controls = true; - container.appendChild(audio); + audioWrap.appendChild(audio); + container.appendChild(audioWrap); activeMedia = audio; } else { - // Replace with video container.innerHTML = ''; var videoWrap = document.createElement('div'); @@ -268,17 +303,16 @@ activeMedia = video; } - // Create new standby standbyMedia = createStandbyMedia(newType); - // Re-add countdown overlay + // Place countdown inside the positioned wrapper (video or audio container) var countdown = document.getElementById('watch-countdown'); - if (countdown) container.appendChild(countdown); + var positionedWrap = container.querySelector('.watch-video-container, .watch-audio-container'); + if (countdown && positionedWrap) positionedWrap.appendChild(countdown); updatePageContent(data); setupMediaEvents(); - // Try autoplay var p = activeMedia.play(); if (p !== undefined) { p.catch(function() { @@ -294,45 +328,34 @@ // --- Update page content (title, description, related, URL) --- function updatePageContent(data) { - // Update page title document.title = data.title; - // Update the heading - var h1 = document.querySelector('.product-images h1, .watch-audio-container + h1, .product-images + h1'); - if (!h1) { - // Try content.j2 layout where h1 is inside product-images - h1 = document.querySelector('h1'); - } - // We only update the text node of the title, preserving the subtitle span + var h1 = document.querySelector('.product-images h1'); + if (!h1) h1 = document.querySelector('h1'); if (h1) { var titleNode = h1.firstChild; if (titleNode && titleNode.nodeType === Node.TEXT_NODE) { - titleNode.textContent = data.title + ' '; + titleNode.textContent = data.title; } } - // Update description var descSection = document.querySelector('.product-description section'); if (descSection) { descSection.innerHTML = data.description_html; } - // Update related content sidebar updateRelated(data.related); - // Update URL if (data.canonical_url) { history.pushState({productId: data.product_id}, data.title, data.canonical_url); } - // Re-render queue renderQueue(); } function updateRelated(related) { var container = document.querySelector('.related-content'); if (!container) { - // Create if it doesn't exist var productRight = document.querySelector('.product-right'); if (!productRight) return; container = document.createElement('div'); @@ -373,7 +396,6 @@ // --- Countdown timer --- function getNextItem() { if (queue.length > 0) return queue[0]; - // Get first related item var firstRelated = document.querySelector('.related-content-item[data-watch-id]'); if (firstRelated) { return { @@ -387,6 +409,8 @@ } function startCountdown() { + if (!autoplayEnabled) return; + var next = getNextItem(); if (!next) return; @@ -431,20 +455,15 @@ } function navigateToNext(next) { - var productId; - if (next && next.id) { - productId = next.id; - // If from queue, remove it - if (queue.length > 0 && queue[0].id === productId) { - queue.shift(); - saveQueue(); - renderQueue(); - } - } else { - return; + if (!next || !next.id) return; + var productId = next.id; + + if (queue.length > 0 && queue[0].id === productId) { + queue.shift(); + saveQueue(); + renderQueue(); } - // Use preloaded data if available and matches if (preloadedData && preloadedData.product_id === productId) { transitionTo(preloadedData); preloadedData = null; @@ -453,6 +472,16 @@ } } + // --- Static content timer (PDF, images: 42 seconds then advance) --- + function startStaticTimer() { + if (!autoplayEnabled) return; + if (staticTimer) clearTimeout(staticTimer); + staticTimer = setTimeout(function() { + staticTimer = null; + startCountdown(); + }, STATIC_DISPLAY_SECONDS * 1000); + } + // --- Preload next media --- function preloadNext() { var next = getNextItem(); @@ -460,7 +489,7 @@ fetchWatchData(next.id).then(function(data) { preloadedData = data; - if (standbyMedia && data.media_url) { + if (standbyMedia && data.media_url && (data.media_type === 'video' || data.media_type === 'audio')) { standbyMedia.src = data.media_url; standbyMedia.preload = 'auto'; } @@ -471,6 +500,7 @@ // --- Media event setup --- function setupMediaEvents() { + if (!activeMedia) return; activeMedia.addEventListener('ended', function() { startCountdown(); }); @@ -480,12 +510,16 @@ }); } - // Initial event setup - setupMediaEvents(); + // Initial setup + if (activeMedia) { + setupMediaEvents(); + } else { + // Static content (PDF, image, etc.) — start 42-second timer + startStaticTimer(); + } // --- Event delegation for SPA link interception --- document.addEventListener('click', function(e) { - // Queue add button var queueBtn = e.target.closest('.queue-add-btn'); if (queueBtn) { e.preventDefault(); @@ -500,7 +534,6 @@ return; } - // Related content links — SPA intercept var link = e.target.closest('.related-content-item[data-watch-id]'); if (link) { e.preventDefault(); @@ -509,9 +542,6 @@ return; } - // Queue item clicks are handled in renderQueue - - // Countdown play now if (e.target.id === 'countdown-play-now' || e.target.closest('#countdown-play-now')) { e.preventDefault(); cancelCountdown(); @@ -520,7 +550,6 @@ return; } - // Countdown cancel if (e.target.id === 'countdown-cancel' || e.target.closest('#countdown-cancel')) { e.preventDefault(); cancelCountdown(); @@ -545,9 +574,6 @@ ); } - // Render queue on load renderQueue(); - - // Preload next on initial load preloadNext(); })(); diff --git a/make_post_sell/templates/content.j2 b/make_post_sell/templates/content.j2 index 1c78a95..4d93634 100644 --- a/make_post_sell/templates/content.j2 +++ b/make_post_sell/templates/content.j2 @@ -44,15 +44,15 @@
-
-