maxman101-stream-display.html
<html lang="en"><head><meta charset="UTF-8"><title>MAXMAN101 Stream Display</title><link rel="preconnect" href="https://fonts.googleapis.com"><link href="https://fonts.googleapis.com/css2?family=Anton&family=Space+Mono:wght@400;700&family=Bitter:ital@0;1&display=swap" rel="stylesheet"><style> :root{ --cardboard:#141414; --cardboard-dark:#0c0c0c; --scoreboard:#1c1c1c; --scoreboard-dark:#0a0a0a; --mustard:#2f86c9; --stamp-red:#d97b3f; --ink:#f4f1ea; --cream:#f4f1ea; --panel-text:#161616; } *{box-sizing:border-box;} body{ margin:0; background:var(--cardboard); background-image: repeating-linear-gradient(0deg, rgba(255,255,255,0.015) 0px, rgba(255,255,255,0.015) 1px, transparent 1px, transparent 3px); color:var(--ink); font-family:'Bitter', serif; min-height:100vh; display:flex; flex-direction:column; } /* ---- TOP MARQUEE ---- */ .marquee-wrap{ background:var(--scoreboard-dark); border-bottom:4px solid var(--mustard); overflow:hidden; white-space:nowrap; padding:10px 0; } .marquee-label{ display:inline-block; background:var(--mustard); color:var(--scoreboard-dark); font-family:'Anton', sans-serif; font-size:14px; letter-spacing:2px; padding:6px 14px; margin-left:16px; transform:skewX(-6deg); } .marquee-track{ display:inline-block; animation:scroll 22s linear infinite; font-family:'Space Mono', monospace; color:var(--cream); font-size:15px; letter-spacing:1px; padding-left:20px; } .marquee-track span{ margin-right:48px; opacity:0.85; } @keyframes scroll{ 0%{ transform:translateX(0%); } 100%{ transform:translateX(-50%); } } /* ---- MAIN LAYOUT ---- */ main{ flex:1; display:grid; grid-template-columns:1.5fr 1fr; gap:24px; padding:28px; } @media (max-width:820px){ main{ grid-template-columns:1fr; } } .main-col{ display:flex; flex-direction:column; } /* ---- NOW SELLING PANEL ---- */ .now-selling{ background:var(--cream); color:var(--panel-text); border:3px solid var(--panel-text); border-radius:4px; position:relative; padding:36px 32px 32px; box-shadow:8px 8px 0 rgba(47,134,201,0.35); } .corner-tag{ position:absolute; top:-18px; left:-18px; background:var(--scoreboard); color:var(--cream); font-family:'Anton', sans-serif; font-size:13px; letter-spacing:1px; padding:10px 14px; border:3px solid var(--panel-text); border-radius:50%; width:56px; height:56px; display:flex; align-items:center; justify-content:center; text-align:center; line-height:1.1; } .eyebrow{ font-family:'Anton', sans-serif; letter-spacing:4px; font-size:13px; color:var(--stamp-red); margin-bottom:6px; } .card-name{ font-family:'Anton', sans-serif; font-size:clamp(32px, 5vw, 56px); line-height:1.02; color:var(--panel-text); margin:0 0 10px; } .card-meta{ font-family:'Space Mono', monospace; font-size:16px; color:var(--scoreboard); margin-bottom:22px; } .card-meta b{ color:var(--panel-text); } .price-row{ display:flex; gap:28px; flex-wrap:wrap; margin-top:8px; } .price-box{ border:2px dashed var(--panel-text); padding:10px 18px; font-family:'Space Mono', monospace; } .price-box .label{ font-size:11px; letter-spacing:2px; opacity:0.65; } .price-box .value{ font-size:24px; font-weight:700; } /* ---- WINNER PHOTO OVERLAY ---- */ .now-selling.winner-active{ padding:0; overflow:hidden; min-height:420px; } .now-selling.winner-active > .eyebrow, .now-selling.winner-active > .card-name, .now-selling.winner-active > .card-meta, .now-selling.winner-active > .price-row{ display:none; } .winner-photo{ display:none; position:relative; width:100%; height:100%; min-height:420px; } .now-selling.winner-active .winner-photo{ display:block; } .winner-photo img{ width:100%; height:100%; min-height:420px; object-fit:cover; display:block; background:var(--scoreboard-dark); } .winner-photo .no-photo{ width:100%; height:100%; min-height:420px; display:flex; align-items:center; justify-content:center; background:linear-gradient(135deg, var(--scoreboard-dark), var(--scoreboard)); color:var(--mustard); font-family:'Anton', sans-serif; font-size:20px; letter-spacing:2px; opacity:0.5; } .winner-gradient{ position:absolute; left:0; right:0; bottom:0; height:55%; background:linear-gradient(to top, rgba(10,10,10,0.92) 15%, rgba(10,10,10,0.55) 55%, transparent 100%); pointer-events:none; } .winner-price-tag{ position:absolute; top:18px; right:18px; background:rgba(10,10,10,0.7); border:2px solid var(--mustard); color:var(--cream); font-family:'Space Mono', monospace; font-weight:700; font-size:20px; padding:8px 14px; border-radius:4px; text-align:right; } .winner-price-tag .sold-label{ display:block; color:var(--stamp-red); font-family:'Anton', sans-serif; font-size:12px; letter-spacing:1px; margin-top:2px; } .winner-info{ position:absolute; left:24px; bottom:78px; display:flex; align-items:center; gap:12px; } .winner-avatar{ width:38px; height:38px; border-radius:50%; background:var(--mustard); color:var(--scoreboard-dark); font-family:'Anton', sans-serif; font-size:16px; display:flex; align-items:center; justify-content:center; flex-shrink:0; } .winner-text{ font-family:'Anton', sans-serif; font-size:20px; color:var(--cream); letter-spacing:0.5px; } .winner-won{ color:var(--stamp-red); margin-left:6px; } .winner-itemline{ position:absolute; left:24px; bottom:20px; right:24px; } .winner-itemline .title{ font-family:'Anton', sans-serif; font-size:22px; color:var(--cream); margin-bottom:2px; } .winner-itemline .sub{ font-family:'Space Mono', monospace; font-size:13px; color:var(--cream); opacity:0.75; } .awaiting-bar{ display:none; margin-top:14px; background:var(--mustard); color:var(--scoreboard-dark); font-family:'Anton', sans-serif; letter-spacing:1px; font-size:14px; text-align:center; padding:12px; border-radius:6px; } .awaiting-bar.show{ display:block; } /* ---- RECENT WINNERS GALLERY ---- */ .winners-strip{ display:flex; gap:10px; overflow-x:auto; padding-bottom:4px; } .winner-thumb{ flex:0 0 auto; width:84px; } .winner-thumb img, .winner-thumb .placeholder{ width:84px; height:84px; object-fit:cover; border-radius:4px; border:2px solid var(--mustard); display:block; background:var(--scoreboard-dark); } .winner-thumb .placeholder{ display:flex; align-items:center; justify-content:center; color:var(--mustard); font-family:'Anton', sans-serif; font-size:22px; opacity:0.6; } .winner-thumb .caption{ font-family:'Space Mono', monospace; font-size:10px; color:var(--cream); opacity:0.8; margin-top:4px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; } .winners-empty{ font-family:'Space Mono', monospace; font-size:12px; opacity:0.5; } /* ---- REVEAL CONTROLS ---- */ .reveal-group{ display:flex; gap:10px; align-items:center; flex-wrap:wrap; } .file-label{ font-family:'Anton', sans-serif; letter-spacing:1px; font-size:14px; border:2px solid var(--panel-text); background:var(--cream); color:var(--panel-text); padding:10px 18px; border-radius:3px; cursor:pointer; display:inline-block; } .file-label input{ display:none; } .file-label.has-photo{ background:var(--mustard); color:var(--cream); } #usernameInput, #streamNameInput{ font-family:'Space Mono', monospace; font-size:14px; padding:9px 12px; border:2px solid var(--panel-text); border-radius:3px; background:var(--cream); color:var(--panel-text); width:160px; } #streamNameInput{ width:220px; } #revealBtn:disabled{ opacity:0.4; cursor:not-allowed; } /* ---- SIDEBAR ---- */ .sidebar{ display:flex; flex-direction:column; gap:20px; } .panel{ background:var(--scoreboard); border-radius:4px; padding:20px 22px; color:var(--cream); } .panel h3{ font-family:'Anton', sans-serif; letter-spacing:2px; font-size:14px; margin:0 0 14px; color:var(--mustard); } /* flip counter */ .flip-row{ display:flex; align-items:baseline; gap:14px; } .flip-digits{ display:flex; gap:6px; } .flip-digit{ background:var(--scoreboard-dark); border:2px solid var(--mustard); border-radius:3px; font-family:'Space Mono', monospace; font-size:38px; font-weight:700; width:38px; text-align:center; padding:4px 0; color:var(--cream); transition:transform 0.15s; } .flip-digit.bump{ transform:translateY(-4px); } .flip-sub{ font-family:'Space Mono', monospace; font-size:13px; opacity:0.75; } /* chase list */ .chase-item{ display:flex; align-items:center; gap:10px; font-family:'Space Mono', monospace; font-size:13px; padding:6px 0; border-bottom:1px solid rgba(247,241,225,0.15); } .chase-item:last-child{ border-bottom:none; } .chase-dot{ width:10px;height:10px;border-radius:50%; background:rgba(247,241,225,0.25); flex-shrink:0; } .chase-item.done .chase-dot{ background:var(--mustard); } .chase-item.done span{ text-decoration:line-through; opacity:0.55; } .chase-progress-bar{ height:8px; background:rgba(247,241,225,0.15); border-radius:4px; overflow:hidden; margin-bottom:14px; } .chase-progress-fill{ height:100%; background:var(--mustard); transition:width 0.4s ease; } /* next up ticker */ .next-item{ display:flex; justify-content:space-between; font-family:'Space Mono', monospace; font-size:13px; padding:5px 0; opacity:0.9; } /* controls (host-only, hide via OBS crop if needed) */ .controls{ padding:16px 28px 28px; display:flex; gap:12px; flex-wrap:wrap; } button{ font-family:'Anton', sans-serif; letter-spacing:1px; font-size:14px; border:2px solid var(--panel-text); background:var(--mustard); color:var(--cream); padding:10px 18px; border-radius:3px; cursor:pointer; } button.secondary{ background:var(--cream); color:var(--panel-text); } button:active{ transform:translateY(1px); } .hint{ font-family:'Space Mono', monospace; font-size:11px; opacity:0.55; align-self:center; }</style></head><body> <div class="marquee-wrap"> <span class="marquee-label">UP NEXT</span> <div class="marquee-track" id="marqueeTrack"></div> </div> <main> <div class="main-col"> <section class="now-selling" id="nowSellingPanel"> <div class="corner-tag" id="lotTag">LOT<br>01</div> <div class="eyebrow">NOW SELLING</div> <h1 class="card-name" id="cardName">1974 Topps Hank Aaron</h1> <div class="card-meta" id="cardMeta"><b>#1</b> Β· All-Time HR Record Β· NM-MT</div> <div class="price-row"> <div class="price-box"> <div class="label">COMP RANGE</div> <div class="value" id="compValue">$180β240</div> </div> <div class="price-box"> <div class="label">STARTING BID</div> <div class="value" id="startValue">$60</div> </div> </div> <div class="winner-photo" id="winnerPhoto"> <img id="winnerImg" style="display:none;"> <div class="no-photo" id="noPhotoFallback">NO PHOTO</div> <div class="winner-gradient"></div> <div class="winner-price-tag" id="winnerPriceTag">$180<span class="sold-label">SOLD</span></div> <div class="winner-info"> <div class="winner-avatar" id="winnerAvatar">M</div> <div class="winner-text"><span id="winnerUsername">maxman101</span><span class="winner-won">won!</span></div> </div> <div class="winner-itemline"> <div class="title" id="winnerItemTitle">1974 Topps Hank Aaron</div> <div class="sub" id="winnerItemSub"><span id="winnerBids">1 Bid</span> Β· Shipping <span id="winnerShip">$3.30 + Taxes</span></div> </div> </div> </section> <div class="awaiting-bar" id="awaitingBar">Awaiting next item</div> </div> <aside class="sidebar"> <div class="panel"> <h3>SOLD TONIGHT</h3> <div class="flip-row"> <div class="flip-digits" id="flipDigits"></div> <div class="flip-sub">items sold</div> </div> </div> <div class="panel"> <h3>RECENT WINNERS</h3> <div class="winners-strip" id="winnersStrip"> <span class="winners-empty">No winners revealed yet</span> </div> </div> <div class="panel"> <h3>CHASE LIST PROGRESS</h3> <div class="chase-progress-bar"> <div class="chase-progress-fill" id="chaseFill"></div> </div> <div id="chaseList"></div> </div> <div class="panel"> <h3>ON DECK</h3> <div id="nextUpList"></div> </div> </aside> </main> <div class="controls"> <button id="nextBtn">βΆ Next Item</button> <div class="reveal-group"> <input type="text" id="streamNameInput" placeholder="stream name (e.g. Vintage Night 7/19)"> <label class="file-label" id="fileLabel"><span id="fileLabelText">π· Add Photo</span><input type="file" id="photoInput" accept="image/*" capture="environment"></label> <input type="text" id="usernameInput" placeholder="winner username"> <button id="revealBtn" class="secondary" disabled>β Reveal Winner</button> </div> <span class="hint" id="logStatus">Host controls β crop these out of your OBS browser source</span> </div><script>// ---- CONFIG ----// Paste your deployed Apps Script Web App URL here (see stream-sales-logger.gs).// Leave blank to run the display without logging to a Sheet.const SHEET_LOG_URL = "https://script.google.com/a/macros/inspiretomorrowmedia.com/s/AKfycbwLhCRqSCesuybpMZsbd39OEz_LIoUvmp-cNc8Z94-R_JkSKdPGxAhggIt3uf0Dnxim/exec";// ---- MOCK DATA (swap this block for a live Sheets/Apps Script fetch later) ----const lots = [ { name:"1974 Topps Hank Aaron", meta:"#1 Β· All-Time HR Record Β· NM-MT", comp:"$180β240", start:"$60", chase:true, bids:"14 Bids", ship:"$3.30 + Taxes" }, { name:"1974 Topps Nolan Ryan", meta:"#20 Β· HOF Pitcher Β· EX-MT", comp:"$90β130", start:"$35", chase:true, bids:"9 Bids", ship:"$3.30 + Taxes" }, { name:"1974 Topps Mike Schmidt RC", meta:"#283 Β· HOF Rookie Β· VG-EX", comp:"$300β450", start:"$120", chase:true, bids:"22 Bids", ship:"$4.50 + Taxes" }, { name:"1974 Topps Pete Rose", meta:"#300 Β· Hit King Β· NM", comp:"$70β100", start:"$25", chase:true, bids:"11 Bids", ship:"$3.30 + Taxes" }, { name:"1974 Topps Dave Parker RC", meta:"#252 Β· HOF Caliber Rookie Β· EX", comp:"$50β75", start:"$18", chase:false, bids:"6 Bids", ship:"$2.50 + Taxes" }, { name:"1974 Topps Washington Padres", meta:"Variation Β· Team Card Β· VG", comp:"$40β65", start:"$15", chase:false, bids:"1 Bid", ship:"$2.50 + Taxes" }, { name:"1974 Topps Dave Winfield RC", meta:"#456 Β· HOF Rookie Β· EX-MT", comp:"$220β300", start:"$85", chase:true, bids:"18 Bids", ship:"$3.30 + Taxes" },];let idx = 0;let soldCount = 0;let pendingPhotoData = null;const winnersHistory = [];const cardName = document.getElementById('cardName');const cardMeta = document.getElementById('cardMeta');const compValue = document.getElementById('compValue');const startValue = document.getElementById('startValue');const lotTag = document.getElementById('lotTag');const flipDigits = document.getElementById('flipDigits');const chaseFill = document.getElementById('chaseFill');const chaseList = document.getElementById('chaseList');const nextUpList = document.getElementById('nextUpList');const marqueeTrack = document.getElementById('marqueeTrack');const nowSellingPanel = document.getElementById('nowSellingPanel');const winnerPhoto = document.getElementById('winnerPhoto');const winnerImg = document.getElementById('winnerImg');const noPhotoFallback = document.getElementById('noPhotoFallback');const winnerPriceTag = document.getElementById('winnerPriceTag');const winnerAvatar = document.getElementById('winnerAvatar');const winnerUsername = document.getElementById('winnerUsername');const winnerItemTitle = document.getElementById('winnerItemTitle');const winnerBids = document.getElementById('winnerBids');const winnerShip = document.getElementById('winnerShip');const awaitingBar = document.getElementById('awaitingBar');const winnersStrip = document.getElementById('winnersStrip');const photoInput = document.getElementById('photoInput');const fileLabel = document.getElementById('fileLabel');const usernameInput = document.getElementById('usernameInput');const streamNameInput = document.getElementById('streamNameInput');const revealBtn = document.getElementById('revealBtn');const logStatus = document.getElementById('logStatus');function renderFlip(n){ const str = String(n).padStart(2,'0'); flipDigits.innerHTML = ''; [...str].forEach(d=>{ const el = document.createElement('div'); el.className = 'flip-digit bump'; el.textContent = d; flipDigits.appendChild(el); setTimeout(()=>el.classList.remove('bump'), 150); });}function renderChase(){ const total = lots.filter(l=>l.chase).length; const done = lots.filter(l=>l.chase && l.sold).length; chaseFill.style.width = (total ? (done/total*100) : 0) + '%'; chaseList.innerHTML = ''; lots.filter(l=>l.chase).forEach(l=>{ const row = document.createElement('div'); row.className = 'chase-item' + (l.sold ? ' done' : ''); row.innerHTML = `<div class="chase-dot"></div><span>${l.name.replace('1974 Topps ','')}</span>`; chaseList.appendChild(row); });}function renderNextUp(){ nextUpList.innerHTML = ''; marqueeTrack.innerHTML = ''; const upcoming = lots.slice(idx+1, idx+4); upcoming.forEach((l,i)=>{ const row = document.createElement('div'); row.className = 'next-item'; row.innerHTML = `<span>${i+1}. ${l.name.replace('1974 Topps ','')}</span><span>${l.comp}</span>`; nextUpList.appendChild(row); }); if(upcoming.length === 0){ nextUpList.innerHTML = '<div class="next-item"><span>β end of lot β</span></div>'; } // marquee needs duplicated content for seamless loop const marqueeItems = upcoming.length ? upcoming : [{name:"End of tonight's lot"}]; const track = marqueeItems.map(l => `<span>${l.name.replace('1974 Topps ','')}</span>`).join(''); marqueeTrack.innerHTML = track + track;}function renderLot(){ const lot = lots[idx]; cardName.textContent = lot.name; cardMeta.innerHTML = lot.meta; compValue.textContent = lot.comp; startValue.textContent = lot.start; lotTag.innerHTML = `LOT<br>${String(idx+1).padStart(2,'0')}`; nowSellingPanel.classList.remove('winner-active'); awaitingBar.classList.remove('show'); pendingPhotoData = null; winnerImg.style.display = 'none'; photoInput.value = ''; usernameInput.value = ''; fileLabel.classList.remove('has-photo'); document.getElementById('fileLabelText').textContent = 'π· Add Photo'; revealBtn.disabled = true; logStatus.textContent = SHEET_LOG_URL ? 'Host controls β crop these out of your OBS browser source' : 'Not logging to a Sheet β add SHEET_LOG_URL in the script to enable.'; renderNextUp();}document.getElementById('nextBtn').addEventListener('click', ()=>{ if(idx < lots.length - 1){ idx++; renderLot(); }});photoInput.addEventListener('change', ()=>{ const file = photoInput.files[0]; if(!file) return; const reader = new FileReader(); reader.onload = (e)=>{ compressImage(e.target.result, 900, 0.65).then((compressed)=>{ pendingPhotoData = compressed; fileLabel.classList.add('has-photo'); document.getElementById('fileLabelText').textContent = 'β Photo Ready'; updateRevealState(); }); }; reader.readAsDataURL(file);});// Resizes to maxWidth and re-encodes as JPEG at the given quality (0-1).// A typical iPhone photo (3-4MB) shrinks to roughly 80-150KB with these settings.function compressImage(dataUrl, maxWidth, quality){ return new Promise((resolve)=>{ const img = new Image(); img.onload = ()=>{ const scale = Math.min(1, maxWidth / img.width); const canvas = document.createElement('canvas'); canvas.width = Math.round(img.width * scale); canvas.height = Math.round(img.height * scale); const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); resolve(canvas.toDataURL('image/jpeg', quality)); }; img.src = dataUrl; });}usernameInput.addEventListener('input', updateRevealState);function updateRevealState(){ revealBtn.disabled = !(usernameInput.value.trim().length > 0);}function renderWinnersStrip(){ if(winnersHistory.length === 0){ winnersStrip.innerHTML = '<span class="winners-empty">No winners revealed yet</span>'; return; } winnersStrip.innerHTML = ''; winnersHistory.slice(0, 10).forEach(w=>{ const el = document.createElement('div'); el.className = 'winner-thumb'; const img = w.photo ? `<img src="${w.photo}" alt="${w.username}">` : `<div class="placeholder">?</div>`; el.innerHTML = `${img}<div class="caption">${w.username}</div>`; winnersStrip.appendChild(el); });}async function logToSheet(payload){ if(!SHEET_LOG_URL){ logStatus.textContent = 'Not logging to a Sheet β add SHEET_LOG_URL in the script to enable.'; return; } logStatus.textContent = 'Saving to Sheetβ¦'; try{ await fetch(SHEET_LOG_URL, { method:'POST', headers:{ 'Content-Type':'text/plain;charset=utf-8' }, // avoids a CORS preflight to Apps Script body: JSON.stringify(payload) }); logStatus.textContent = 'Saved to Sheet β'; }catch(err){ console.error('Sheet log failed', err); logStatus.textContent = 'Sheet log failed β check SHEET_LOG_URL / connection.'; }}revealBtn.addEventListener('click', ()=>{ const lot = lots[idx]; const username = usernameInput.value.trim(); if(!username) return; lot.sold = true; soldCount++; renderFlip(soldCount); renderChase(); if(pendingPhotoData){ winnerImg.src = pendingPhotoData; winnerImg.style.display = 'block'; noPhotoFallback.style.display = 'none'; } else { winnerImg.style.display = 'none'; noPhotoFallback.style.display = 'flex'; } winnerAvatar.textContent = username.charAt(0).toUpperCase(); winnerUsername.textContent = username; winnerItemTitle.textContent = lot.name.replace('1974 Topps ',''); winnerBids.textContent = lot.bids; winnerShip.textContent = lot.ship; winnerPriceTag.innerHTML = `${lot.start}<span class="sold-label">SOLD</span>`; nowSellingPanel.classList.add('winner-active'); awaitingBar.classList.add('show'); winnersHistory.unshift({ username, photo:pendingPhotoData, item:lot.name }); renderWinnersStrip(); logToSheet({ streamName: streamNameInput.value.trim() || 'Unsorted', lotNumber: idx + 1, itemName: lot.name, winner: username, price: lot.start, bids: lot.bids, shipping: lot.ship, photo: pendingPhotoData || '' });});// initrenderFlip(0);renderChase();renderLot();renderWinnersStrip();</script></body></html>