[object Object]

← back to Dead Agentabrams

Music room: CONSOLIDATE the two video-backdrop systems into ONE controller (DTD verdict B, TK-11178)

8f1b9bfef0a56d7074fe1ce8df588b6c7a639259 · 2026-09-03 11:52:03 -0700 · Steve (via Claude)

- Deleted the duplicate #vrBackdrop layer + vrEmbed in-panel player + showBackdrop/hideBackdrop/bdToggle + .vr-bg CSS + dead .vr-embed/.vr-bd-toggle CSS
- The Videos panel now browses MR_VIDEOS and each pick is a MANUAL trigger into the one VideoBackdrop controller (VB) via new playConcertManual(): plays the chosen concert as the #venueVideo backdrop + audio, kills archive audio (single audio owner), yt-hero + react-disabled like the auto path
- Kept the 'Backdrop size' control, retargeted onto #venueVideo via --vr-bd-scale
- Two triggers (auto era-match failover + manual pick) -> one engine, one backdrop, one audio owner: double-audio is now structurally impossible
- Verified via Playwright: #vrBackdrop gone, 18-row browser intact, pick drives #venueVideo, archive audio killed, no .vr-bg

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 8f1b9bfef0a56d7074fe1ce8df588b6c7a639259
Author: Steve (via Claude) <steve@designerwallcoverings.com>
Date:   Thu Sep 3 11:52:03 2026 -0700

    Music room: CONSOLIDATE the two video-backdrop systems into ONE controller (DTD verdict B, TK-11178)
    
    - Deleted the duplicate #vrBackdrop layer + vrEmbed in-panel player + showBackdrop/hideBackdrop/bdToggle + .vr-bg CSS + dead .vr-embed/.vr-bd-toggle CSS
    - The Videos panel now browses MR_VIDEOS and each pick is a MANUAL trigger into the one VideoBackdrop controller (VB) via new playConcertManual(): plays the chosen concert as the #venueVideo backdrop + audio, kills archive audio (single audio owner), yt-hero + react-disabled like the auto path
    - Kept the 'Backdrop size' control, retargeted onto #venueVideo via --vr-bd-scale
    - Two triggers (auto era-match failover + manual pick) -> one engine, one backdrop, one audio owner: double-audio is now structurally impossible
    - Verified via Playwright: #vrBackdrop gone, 18-row browser intact, pick drives #venueVideo, archive audio killed, no .vr-bg
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 room/index.html | 101 +++++++++++++++++++++-----------------------------------
 1 file changed, 38 insertions(+), 63 deletions(-)

diff --git a/room/index.html b/room/index.html
index 13502ac..f1884e2 100644
--- a/room/index.html
+++ b/room/index.html
@@ -26,7 +26,8 @@
   #orb{z-index:4;pointer-events:none}
   /* ---- Concert-video backdrop (TK-11178): official GD footage behind the generative scene ---- */
   #venueVideo{position:absolute;inset:0;overflow:hidden;z-index:0;pointer-events:none;background:#000;opacity:0;transition:opacity 1.4s ease}
-  #venueVideo iframe,#venueVideo>div{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
+  #venueVideo iframe,#venueVideo>div{position:absolute;top:50%;left:50%;
+    transform:translate(-50%,-50%) scale(var(--vr-bd-scale,1));   /* size dialable from the Videos panel (TK-11178) */
     width:100vw;height:56.25vw;min-height:100vh;min-width:177.78vh;border:0;pointer-events:none}
   #stage.video-bg #venueVideo{opacity:1}
   #stage.video-bg #sky{opacity:.12}
@@ -34,21 +35,9 @@
   /* YouTube fallback = the video IS the show → let the concert footage dominate the frame */
   #stage.video-bg.yt-hero #sky{opacity:.04}
   #stage.video-bg.yt-hero #scene{opacity:.16}
-  /* ---- Videos-panel backdrop — a SEPARATE layer from #venueVideo/TK-11178's
-     archive-tied backdrop, so picking a video in the standalone Videos panel
-     never touches that system's state. Muted (the panel's own iframe carries sound). ---- */
-  /* A drapery hung behind the stage, not a wallpaper covering it — the WHOLE frame stays
-     visible (letterboxed), never cropped/zoomed into an illegible close-up. */
-  #vrBackdrop{position:absolute;inset:0;overflow:hidden;z-index:0;pointer-events:none;background:#000;opacity:0;transition:opacity 1.4s ease;
-    display:flex;align-items:center;justify-content:center}
-  #vrBackdrop iframe{border:0;pointer-events:none;
-    width:min(100vw, 177.78vh); height:min(100vh, 56.25vw);
-    transform:scale(var(--vr-bd-scale, .82));}
-  /* dimmer + a dark scrim so it reads as BACKGROUND — the generative graphics stay the foreground */
-  #stage.vr-bg #vrBackdrop{opacity:.5}
-  #stage.vr-bg #vrBackdrop::after{content:'';position:absolute;inset:0;background:rgba(8,3,4,.45)}
-  #stage.vr-bg #sky{opacity:.20}
-  #stage.vr-bg #scene{opacity:.85}
+  /* (TK-11178 consolidation) The separate #vrBackdrop/.vr-bg layer was removed — there is now
+     ONE backdrop (#venueVideo), driven by both the automatic era-match failover AND the manual
+     Videos-panel pick. Its size is dialable via --vr-bd-scale (Videos panel "Backdrop size"). */
   #stage.video-bg.yt-hero #orb{opacity:.5}
   .btn:disabled,button:disabled{opacity:.42;cursor:not-allowed}
   /* ---- Controls in a collapsed RIGHT drawer, opened by the top-right hamburger (TK-11178 UI) ---- */
@@ -288,14 +277,7 @@
   .videoroom[data-open="false"] .chev{transform:rotate(-90deg)}
   .videoroom[data-open="false"] .vrbody{display:none}
   .vrbody{padding:0 14px 14px}
-  .vr-embed{position:relative;width:100%;aspect-ratio:16/9;border-radius:9px;overflow:hidden;background:#000;margin-bottom:9px}
-  .vr-embed iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
-  .vr-embed:empty::after{content:'Pick a show below';display:flex;align-items:center;justify-content:center;
-    height:100%;color:var(--ink-dim);font-style:italic;font-size:11.5px}
-  .vr-bd-toggle{display:block;width:100%;background:rgba(40,12,16,.7);color:var(--ink-dim);
-    border:1px solid var(--panel-edge);border-radius:9px;padding:6px 10px;font-family:var(--sans);
-    font-size:11px;cursor:pointer;margin-bottom:8px}
-  .vr-bd-toggle[aria-pressed="true"]{color:var(--gold);border-color:rgba(242,182,74,.5)}
+  .vr-hint{font-size:11px;font-style:italic;color:var(--ink-dim);margin-bottom:9px}
   .vr-size{margin-bottom:9px}
   .vr-size label{display:block;font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-dim);margin-bottom:4px}
   .vr-size .val{font-family:var(--serif);font-style:italic;color:var(--gold);font-size:12px;text-transform:none;letter-spacing:0}
@@ -354,7 +336,6 @@
   <!-- role removed from <main>: role="application" suppressed normal screen-reader navigation over the native controls + prose -->
 
   <div id="venueVideo" aria-hidden="true"><div id="ytHost"></div></div>
-  <div id="vrBackdrop" aria-hidden="true"></div>
   <canvas id="sky" aria-hidden="true"></canvas>
   <canvas id="scene" aria-hidden="true"></canvas>
   <canvas id="orb" aria-hidden="true"></canvas>
@@ -404,7 +385,7 @@
       <span class="chev" aria-hidden="true">▾</span>
     </header>
     <div class="vrbody" id="vrbody">
-      <div class="vr-embed" id="vrEmbed"></div>
+      <div class="vr-hint">Pick a concert — it plays on the stage.</div>
       <div class="vr-list" id="vrList" role="list" aria-label="Concert videos"></div>
     </div>
   </section>
@@ -1699,17 +1680,14 @@ const MR_VIDEOS=[
   {id:'sSPyP9qwkpA',year:1995,title:'Soldier Field · 1995 (final era)'}
 ];
 
-// ---------- Standalone Videos panel — browse + watch the concert-video library
-// directly. Deliberately independent of archive.org/VB/Music Room: a plain
-// YouTube iframe embed, so it still works when the archive-audio pipeline is down. ----------
+// ---------- Videos panel — browse the concert library; a pick is a MANUAL TRIGGER into the ONE
+// VideoBackdrop controller (VB), which plays it as the stage backdrop + audio. (TK-11178
+// consolidation: the old separate #vrBackdrop/vrEmbed layer + .vr-bg were removed.) ----------
 (function(){
   const vr = document.getElementById('videoroom');
   const vrToggle = document.getElementById('vrToggle');
   const vrList = document.getElementById('vrList');
-  const vrEmbed = document.getElementById('vrEmbed');
-  const vrBackdrop = document.getElementById('vrBackdrop');
-  const stageEl = document.getElementById('stage');
-  if (!vr || !vrToggle || !vrList || !vrEmbed || !vrBackdrop || !stageEl) return;
+  if (!vr || !vrToggle || !vrList) return;
   function setOpen(open){
     vr.dataset.open = open ? 'true' : 'false';
     vrToggle.setAttribute('aria-expanded', open ? 'true' : 'false');
@@ -1718,44 +1696,21 @@ const MR_VIDEOS=[
   vrToggle.addEventListener('keydown', e => {
     if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); vrToggle.click(); }
   });
-  function hideBackdrop(){
-    stageEl.classList.remove('vr-bg');
-    vrBackdrop.innerHTML = '';
-  }
-  function showBackdrop(v){
-    // muted — the panel's own iframe below carries the sound; this is a silent visual layer
-    vrBackdrop.innerHTML = `<iframe src="https://www.youtube-nocookie.com/embed/${v.id}?autoplay=1&mute=1&loop=1&playlist=${v.id}&controls=0&rel=0" title="" tabindex="-1" allow="autoplay" loading="lazy"></iframe>`;
-    stageEl.classList.add('vr-bg');
-  }
-  const bdToggle = document.createElement('button');
-  bdToggle.type = 'button'; bdToggle.className = 'vr-bd-toggle'; bdToggle.setAttribute('aria-pressed', 'true');
-  bdToggle.textContent = '🖼 Behind the art: on';
-  bdToggle.addEventListener('click', () => {
-    const on = bdToggle.getAttribute('aria-pressed') === 'true';
-    bdToggle.setAttribute('aria-pressed', on ? 'false' : 'true');
-    bdToggle.textContent = on ? '🖼 Behind the art: off' : '🖼 Behind the art: on';
-    if (on) hideBackdrop(); else if (vr.dataset.curId) showBackdrop({ id: vr.dataset.curId });
-  });
-  vrEmbed.insertAdjacentElement('beforebegin', bdToggle);
-
-  // size control — how large the backdrop video renders, so it can be dialed to taste
+  // Backdrop size — dials the ONE #venueVideo backdrop via --vr-bd-scale (wrapped for private-mode).
   const SCALE_KEY = 'vr-bd-scale';
-  // clamp — an old saved value from the pre-fix cover/crop version (which allowed up to 200%)
-  // would look far too large now that the backdrop is fully-contained, never cropped
-  const savedScale = Math.min(1.5, Math.max(.4, parseFloat(localStorage.getItem(SCALE_KEY)) || .82));
-  document.documentElement.style.setProperty('--vr-bd-scale', savedScale);
+  const savedScale = Math.min(1.5, Math.max(.4, parseFloat((()=>{try{return localStorage.getItem(SCALE_KEY);}catch(e){return null;}})()) || 1));
+  try{ document.documentElement.style.setProperty('--vr-bd-scale', savedScale); }catch(e){}
   const sizeWrap = document.createElement('div');
   sizeWrap.className = 'vr-size';
   sizeWrap.innerHTML = `<label for="vrSize">Backdrop size <span class="val" id="vrSizeV">${Math.round(savedScale*100)}%</span></label>
     <input type="range" id="vrSize" min="40" max="150" step="5" value="${Math.round(savedScale*100)}" aria-label="Backdrop video size">`;
-  vrEmbed.insertAdjacentElement('beforebegin', sizeWrap);
+  vrList.insertAdjacentElement('beforebegin', sizeWrap);
   const sizeInput = sizeWrap.querySelector('#vrSize');
   const sizeVal = sizeWrap.querySelector('#vrSizeV');
   sizeInput.addEventListener('input', () => {
     const pct = +sizeInput.value;
-    document.documentElement.style.setProperty('--vr-bd-scale', pct / 100);
+    try{ document.documentElement.style.setProperty('--vr-bd-scale', pct / 100); localStorage.setItem(SCALE_KEY, pct / 100); }catch(e){}
     sizeVal.textContent = pct + '%';
-    localStorage.setItem(SCALE_KEY, pct / 100);
   });
   MR_VIDEOS.forEach(v => {
     const row = document.createElement('button');
@@ -1767,8 +1722,7 @@ const MR_VIDEOS=[
       vrList.querySelectorAll('.vr-row.on').forEach(r => r.classList.remove('on'));
       row.classList.add('on');
       vr.dataset.curId = v.id;
-      vrEmbed.innerHTML = `<iframe src="https://www.youtube-nocookie.com/embed/${v.id}?autoplay=1&rel=0" title="${v.title.replace(/"/g,'&quot;')}" allow="accelerometer;autoplay;clipboard-write;encrypted-media;gyroscope;picture-in-picture" allowfullscreen loading="lazy"></iframe>`;
-      if (bdToggle.getAttribute('aria-pressed') === 'true') showBackdrop(v);
+      playConcertManual(v.id, v.title);   // manual trigger → the one backdrop controller (hoisted from the Music Room IIFE)
     });
     vrList.appendChild(row);
   });
@@ -1894,6 +1848,27 @@ function enterYouTubeFallback(show, hadGesture){
   announce('archive.org is unreachable — playing the nearest official Grateful Dead concert from YouTube.');
   saveResume();                                            // persist the selected show even in fallback (audio timeupdate won't fire here)
 }
+// MANUAL trigger from the Videos panel → play a chosen concert through the ONE backdrop controller (TK-11178).
+// Same engine as the automatic failover, different entry point → one backdrop layer, one audio owner.
+function playConcertManual(id, title){
+  VB.mode='youtube';
+  try{ audio.pause(); }catch(e){}
+  try{ audio.removeAttribute('src'); audio.load(); }catch(e){}   // one audio owner — kill archive audio
+  const hasAct=!!(navigator.userActivation ? navigator.userActivation.isActive : false);
+  VB.wantId=id; VB.wantPlay=true; VB.wantMuted=!hasAct; VB._tried=new Set(); VB._hops=0;
+  _loadYTApi();
+  if(VB.player && VB.player.loadVideoById) _applyVideo();
+  _vbShow(); _stageEl.classList.add('yt-hero');
+  if(!hasAct) _primeYTUnlock();
+  $('mrNow').textContent='▶ Grateful Dead — '+(title||'concert');
+  $('mrSub').textContent='Playing your pick from the Videos panel — official concert footage on YouTube.';
+  const msg=$('mrMsg'); if(msg){ msg.hidden=false;
+    msg.innerHTML='Now playing the concert you chose.'+(hasAct?'':' Tap or press any key for sound.')+
+      ' <a href="https://www.youtube.com/watch?v='+id+'" target="_blank" rel="noopener noreferrer">Watch on YouTube ↗</a>'; }
+  setPlay(true);
+  if(typeof reactBtn!=='undefined' && reactBtn){ reactBtn.disabled=true; reactBtn.title='Live audio analysis isn’t available on the YouTube stream.'; }
+  saveResume();
+}
 function vbTogglePlay(){
   const p=VB.player; if(!p || !window.YT) return;
   const st=p.getPlayerState?p.getPlayerState():-1;

← 8e80e02 Backdrop: full drapery behind the stage, never a cropped clo  ·  back to Dead Agentabrams  ·  Backdrop: confine to the stage-riser area with live X/Y/W/H f5a3143 →