← 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,'"')}" 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 →