From 98efbfe566df562f11a6d8493af91e8d6d95aa36 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Mon, 9 Feb 2026 10:57:56 -0500 Subject: [PATCH] Track ring loop completions with +N badge and show mod badge for shop owners When a viewer watches every item in the ring, the loop counter increments, history resets (un-dims all items), and a +1/+2/+3 badge appears next to the progress display. Shop owners/editors get a green "mod" badge. Ring state: ringLoops persisted in localStorage alongside existing ring state. Server: watch_json now returns is_mod flag. Templates pass data-watch-is-mod. --- make_post_sell/static/css/common.css | 18 ++++++++++ make_post_sell/static/js/watch.js | 52 ++++++++++++++++++++++++++-- make_post_sell/templates/content.j2 | 2 +- make_post_sell/templates/product.j2 | 2 +- make_post_sell/views/watch.py | 6 ++++ 5 files changed, 76 insertions(+), 4 deletions(-) diff --git a/make_post_sell/static/css/common.css b/make_post_sell/static/css/common.css index eb64043..a85a222 100644 --- a/make_post_sell/static/css/common.css +++ b/make_post_sell/static/css/common.css @@ -2767,6 +2767,24 @@ textarea { color: var(--text-muted, #999); font-variant-numeric: tabular-nums; } +.ring-loops-badge { + font-size: 0.75em; + font-weight: bold; + padding: 1px 6px; + border-radius: 10px; + background-color: var(--blue-color, #5871ad); + color: white; +} +.ring-mod-badge { + font-size: 0.65em; + font-weight: bold; + text-transform: uppercase; + letter-spacing: 0.05em; + padding: 1px 5px; + border-radius: 10px; + background-color: #48bb78; + color: white; +} .watch-direction-label { display: grid; diff --git a/make_post_sell/static/js/watch.js b/make_post_sell/static/js/watch.js index f2f3c5c..9f341cf 100644 --- a/make_post_sell/static/js/watch.js +++ b/make_post_sell/static/js/watch.js @@ -24,6 +24,7 @@ var ringPosition = 0; var ringDirection = 1; // 1 = forward, -1 = reverse var ringHistory = {}; // { productId: true } — set of watched IDs + var ringLoops = 0; // completed full circuits of the ring // Load ring state from localStorage try { @@ -42,6 +43,10 @@ var savedHistory = localStorage.getItem('watchRingHistory'); if (savedHistory) ringHistory = JSON.parse(savedHistory); } catch (e) {} + try { + var savedLoops = localStorage.getItem('watchRingLoops'); + if (savedLoops !== null) ringLoops = parseInt(savedLoops, 10) || 0; + } catch (e) {} function saveRingState() { try { @@ -49,12 +54,31 @@ localStorage.setItem('watchRingPosition', String(ringPosition)); localStorage.setItem('watchRingDirection', String(ringDirection)); localStorage.setItem('watchRingHistory', JSON.stringify(ringHistory)); + localStorage.setItem('watchRingLoops', String(ringLoops)); } catch (e) {} } function markWatched(productId) { if (!productId) return; ringHistory[productId] = true; + + // Check for complete loop — all ring items watched + if (ringProductIds.length > 0) { + var allWatched = true; + for (var i = 0; i < ringProductIds.length; i++) { + if (!ringHistory[ringProductIds[i]]) { + allWatched = false; + break; + } + } + if (allWatched) { + ringLoops++; + ringHistory = {}; + // Current item starts the new loop as already watched + ringHistory[productId] = true; + } + } + saveRingState(); updateProgressDisplay(); } @@ -73,7 +97,7 @@ } } - // Update progress display ("23 / 87") + // Update progress display ("23 / 87") and loop badge function updateProgressDisplay() { var el = document.getElementById('ring-progress'); if (!el) return; @@ -86,6 +110,17 @@ if (ringProductIds.indexOf(id) !== -1) watched++; } el.textContent = watched + ' / ' + ringProductIds.length; + + // Loop badge + var badge = document.getElementById('ring-loops-badge'); + if (badge) { + if (ringLoops > 0) { + badge.textContent = '+' + ringLoops; + badge.style.display = ''; + } else { + badge.style.display = 'none'; + } + } } // Autoplay preference (persisted in localStorage) @@ -119,12 +154,18 @@ if (savedQueue) queue = JSON.parse(savedQueue); } catch (e) {} + // Mod status (shop owner/editor) + var isMod = false; + // Mark current item as watched and sync ring position var initialProductEl = document.querySelector('[data-watch-product-id]'); if (initialProductEl) { var initialId = initialProductEl.getAttribute('data-watch-product-id'); markWatched(initialId); syncRingPosition(initialId); + if (initialProductEl.getAttribute('data-watch-is-mod') === '1') { + isMod = true; + } } // --- Autoplay with sound (for video/audio) --- @@ -448,10 +489,11 @@ // --- Update page content (title, description, related, URL) --- function updatePageContent(data) { - // Update ring from server response + // Update ring and mod status from server response if (data.ring && data.ring.length) { ringProductIds = data.ring; } + if (data.is_mod) isMod = true; markWatched(data.product_id); syncRingPosition(data.product_id); @@ -492,9 +534,15 @@ var autoplayChecked = autoplayEnabled ? ' checked' : ''; var directionChecked = ringDirection === -1 ? ' checked' : ''; + var modBadge = isMod ? 'mod' : ''; + var loopsBadge = ringLoops > 0 + ? '+' + ringLoops + '' + : ''; var headerHtml = '