← back to Dead Agentabrams
room: cancel stale archive fetches on source-switch (contrarian hardening)
ac0f021961964ba1a6aa0c7c91e9473842db15fa · 2026-09-02 09:36:17 -0700 · Steve Abrams
Files touched
Diff
commit ac0f021961964ba1a6aa0c7c91e9473842db15fa
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Wed Sep 2 09:36:17 2026 -0700
room: cancel stale archive fetches on source-switch (contrarian hardening)
---
room/index.html | 152 ++++++++++----------------------------------------------
1 file changed, 27 insertions(+), 125 deletions(-)
diff --git a/room/index.html b/room/index.html
index 1e48d0b..ac0394c 100644
--- a/room/index.html
+++ b/room/index.html
@@ -237,10 +237,6 @@
}
@media (prefers-reduced-motion: reduce){
.museum,.chapters button,.btn,.skip{transition:none}
- /* also silence the always-on keyframe loops (live-dot pulse, loading tiles) */
- *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important}
- .musicroom.playing .live-dot{animation:none}
- .mr-yr.loading{animation:none}
}
</style>
</head>
@@ -270,9 +266,9 @@
</header>
<div class="mrbody" id="mrbody">
<input class="mr-search" id="mrSearch" type="search" placeholder="🔍 Search the archive — date, city, song…" aria-label="Search the Grateful Dead archive">
- <div class="mr-src" id="mrSrc" role="group" aria-label="Choose a music source"></div>
+ <div class="mr-src" id="mrSrc" role="tablist" aria-label="Choose a music source"></div>
<div class="mr-map-head"><span id="mrMapLabel">Tap a year</span><button type="button" class="mr-allbtn on" id="mrAll">All years</button></div>
- <div class="mr-map" id="mrMap" role="group" aria-label="Browse shows by year"></div>
+ <div class="mr-map" id="mrMap" role="listbox" aria-label="Browse shows by year"></div>
<input type="hidden" id="mrYear" value="">
<div class="mr-results" id="mrResults" role="list"></div>
<div class="mr-picks">Curated nights:</div>
@@ -405,18 +401,6 @@ const CHAPTER_SECONDS = 55; // dwell before the boat drifts to the next chapter
if (window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches){
S.reduced = true;
}
-// ...and keep honoring it if the visitor flips the OS setting mid-session
-if (window.matchMedia){
- const _rmq = matchMedia('(prefers-reduced-motion: reduce)');
- const _sync = e => {
- S.reduced = e.matches;
- const rb = document.getElementById('reduceBtn');
- if (rb) rb.setAttribute('aria-pressed', String(S.reduced));
- if (S.reduced) ctx.clearRect(0,0,S.W,S.H);
- };
- if (_rmq.addEventListener) _rmq.addEventListener('change', _sync);
- else if (_rmq.addListener) _rmq.addListener(_sync);
-}
// ---------- Palettes + per-chapter choreography ----------
const CHAPTERS = [
@@ -966,7 +950,7 @@ function drawAnimals(){
heron(W*0.40, baseY-4, 1*scaleA, t, accent);
rabbit(W*0.63, baseY-4, 1*scaleA, t, accent);
bearAndFireflies(W*0.85, baseY-2, 1.1*scaleA, t, accent);
- if(!S.playing && !S.reduced){ drawIdleBackflipBear(scaleA, baseY); } else { drawBearCubs(t, scaleA, baseY); }
+ drawBearCubs(t, scaleA, baseY);
drawSongbirds(t, scaleA, baseY);
ctx.restore();
@@ -1103,21 +1087,6 @@ function bearCub(x,y,s,t,ph,FUR){
ctx.fillStyle=OUTLINE; ctx.beginPath(); ctx.arc(0,-29,1.1,0,7); ctx.fill(); // nose
ctx.restore();
}
-function drawIdleBackflipBear(scaleA, baseY){
- const W=S.W;
- const cross=8.5, gap=1.4, span=cross+gap;
- const tt=S.idleT||0; const idx=Math.floor(tt/span); const local=tt%span;
- if(local>cross) return;
- const prog=local/cross;
- const FUR=BEAR_COLORS[idx%BEAR_COLORS.length];
- const size=1.55*scaleA, margin=64;
- const x=-margin + prog*(W+margin*2);
- const flips=3, fp=(prog*flips)%1;
- const lift=Math.sin(fp*Math.PI)*54*scaleA;
- const spin=-(prog*flips)*Math.PI*2;
- ctx.save(); ctx.translate(x,(baseY+8)-lift); ctx.rotate(spin);
- bearCub(0,0,size,(S.idleT||0)*0.8,idx*1.3,FUR); ctx.restore();
-}
// a user-sized colorful dancing-bear troupe (1..10), auto-distributed + scaled across the shore (our own design, not the GD trademark)
const BEAR_COLORS=['rgba(206,66,116,0.98)','rgba(232,160,52,0.98)','rgba(72,172,150,0.98)','rgba(124,110,224,0.98)','rgba(226,96,60,0.98)','rgba(96,182,96,0.98)','rgba(232,120,182,0.98)','rgba(82,158,224,0.98)','rgba(214,182,60,0.98)','rgba(154,92,204,0.98)'];
function drawBearCubs(t,scaleA,baseY){
@@ -1266,21 +1235,18 @@ function drawPtero(dt){
let last=performance.now();
function frame(now){
const dt=Math.min(0.05,(now-last)/1000); last=now;
- S.idleT=(S.idleT||0)+dt;
// live audio energy (0..1) when "React to Music" is on — otherwise motion stays authored
const reacting = !!(window.SRM && window.SRM.react);
S.amp = reacting ? (window.SRM.level||0) : 0;
S.pulse = reacting ? (window.SRM.pulse||0) : 0; // sharp per-beat throb for lamps
- // bridge visual state to the separate WebGL orb loop so it can honor reduced motion + pause
- if(window.SRM){ window.SRM.reduced = S.reduced; window.SRM.playing = S.playing; }
// fire a burst on each new detected beat
if(reacting && !S.reduced && window.SRM.beat>_seenBeat){ _seenBeat=window.SRM.beat; spawnFirework(); }
else if(!reacting){ _seenBeat=window.SRM?(window.SRM.beat||0):0; } // let fireworks decay naturally
if(S.playing){
const spd = S.reduced ? 0.28 : 1.0;
- S.t += dt * S.tempo * spd * 0.85; // slowed jazzy/groovy
+ S.t += dt * S.tempo * spd * 1.4;
// Gentle auto-journey through the three chapters (until the visitor takes over)
if(S.autoJourney && !S.reduced){
@@ -1497,26 +1463,13 @@ requestAnimationFrame(frame);
(() => {
"use strict";
const CATALOG = [
- {id:"gd69-02-27.sbd.16track.kaplan.6315.sbeok.shnf", date:"February 27, 1969", title:"Fillmore West", sub:"San Francisco · primal psychedelic era"},
- {id:"gd70-05-24.sbd.hanno.6481.sbeok.shnf", date:"May 24, 1970", title:"Newcastle-Under-Lyme", sub:"Newcastle · acoustic-and-electric era"},
- {id:"gd71-04-14.sbd.lai.6250.sbefail.shnf", date:"April 14, 1971", title:"Christy Mathewson Stadium, Bucknell U.", sub:"Lewisburg · Skull & Roses era"},
- {id:"gd72-08-27.sbd.orf.3328.sbeok.shnf", date:"August 27, 1972", title:"Veneta", sub:"Oregon · field, heat, and open sky"},
- {id:"gd73-02-15.sbd.hall.1580.sbeok.shnf", date:"February 15, 1973", title:"Dane County Coliseum", sub:"Madison · jazz-loose / Wake of the Flood era"},
- {id:"gd74-06-23.sbd.cribbs.16780.sbeok.shnf", date:"June 23, 1974", title:"Jai-Alai Fronton", sub:"Miami · Wall of Sound era"},
- {id:"gd76-06-10.sbd.bertha-ashley.23370.sbeok.shnf", date:"June 10, 1976", title:"Boston Music Hall", sub:"Boston · return-from-hiatus era"},
- {id:"gd77-05-08.sbd.hicks.4982.sbeok.shnf", date:"May 8, 1977", title:"Cornell", sub:"Ithaca · Barton Hall"},
- {id:"gd78-02-01.sbd.parillo.1017.sbeok.shnf", date:"February 1, 1978", title:"Uptown Theater", sub:"Chicago · tight & funky late-’70s era"},
- {id:"gd81-03-21.sbd.clugston.6666.sbeok.shnf", date:"March 21, 1981", title:"Rainbow Theatre", sub:"London · European-tour era"},
- {id:"gd85-04-27.sbd.jerugim.359.sbeok.shnf", date:"April 27, 1985", title:"Frost Amphitheatre", sub:"Palo Alto · 20th-anniversary / Frost era"},
- {id:"gd1987-01-28.sbd.walker-scotton.miller.83286.flac16", date:"January 28, 1987", title:"S.F Civic Auditorium", sub:"San Francisco · In the Dark comeback era"},
- {id:"gd89-12-31.sbd.bertha-ashley.18674.sbeok.shnf", date:"December 31, 1989", title:"Oakland-Alameda County Coliseum", sub:"Oakland · late-peak / Built to Last era"},
- {id:"gd90-03-29.aud-fob.set2.unknown.1317.sbeok.shnf", date:"March 29, 1990", title:"Nassau", sub:"Uniondale · late-era expansion"},
- {id:"gd91-10-31.sbd.gardner.2897.sbeok.shnf", date:"October 31, 1991", title:"Oakland-Alameda County Coliseum", sub:"Oakland · post-Brent / Bruce Hornsby era"},
- {id:"gd93-03-24.sbd.keyser.25664.sbeok.flacf", date:"March 24, 1993", title:"Dean Smith Center", sub:"Chapel Hill · Vince era / stadium years"},
- {id:"gd94-10-05.sbd.unknown.8030.sbeok.shnf", date:"October 5, 1994", title:"The Spectrum", sub:"Philadelphia · final-years era"},
- {id:"gd95-07-09.sbd.7233.sbeok.shnf", date:"July 9, 1995", title:"Soldier Field", sub:"Chicago · the final show"}
+ {id:'gd69-02-27.sbd.16track.kaplan.6315.sbeok.shnf', date:'February 27, 1969', title:'Fillmore West', sub:'San Francisco · primal psychedelic era'},
+ {id:'gd72-08-27.sbd.orf.3328.sbeok.shnf', date:'August 27, 1972', title:'Veneta', sub:'Oregon · field, heat, and open sky'},
+ {id:'gd74-06-23.sbd.cribbs.16780.sbeok.shnf', date:'June 23, 1974', title:'Jai-Alai Fronton', sub:'Miami · Wall of Sound era'},
+ {id:'gd77-05-08.sbd.hicks.4982.sbeok.shnf', date:'May 8, 1977', title:'Cornell', sub:'Ithaca · Barton Hall'},
+ {id:'gd90-03-29.aud-fob.set2.unknown.1317.sbeok.shnf',date:'March 29, 1990', title:'Nassau', sub:'Uniondale · late-era expansion'}
];
-const DEFAULT_SHOW = 7; // Cornell '77
+const DEFAULT_SHOW = 3; // Cornell '77
const $ = id => document.getElementById(id);
const audio = $('mrAudio'), room = $('musicroom'), sel = $('mrShow'), live = $('live');
@@ -1577,9 +1530,13 @@ async function loadShow(i, opts){ if(sel) sel.value=String(i); return loadShowRe
let searchTimer=null;
// archive.org is a flaky nonprofit — abort a hung request so the UI fails gracefully
// into the error state instead of pulsing "…" forever (5x sweep 2 fix).
-function fetchT(url, ms=9000){
+function fetchT(url, ms=9000, extSignal){
const ctl = (typeof AbortController!=='undefined') ? new AbortController() : null;
const t = ctl ? setTimeout(()=>ctl.abort(), ms) : null;
+ if(ctl && extSignal){ // batch-cancel (e.g. source switch)
+ if(extSignal.aborted) ctl.abort();
+ else extSignal.addEventListener('abort', ()=>ctl.abort(), {once:true});
+ }
return fetch(url, {mode:'cors', signal: ctl?ctl.signal:undefined})
.finally(()=>{ if(t) clearTimeout(t); });
}
@@ -1592,7 +1549,7 @@ async function searchArchive(q){
const filters=[]; if(q) filters.push('('+q+')'); if(yr) filters.push('year:'+yr);
const query = filters.length ? coll+' AND '+filters.join(' AND ') : coll;
res.innerHTML='<div class="mr-hint">'+(filters.length?'Searching the archive…':'Loading the most-played shows…')+'</div>';
- const sort = (q||yr) ? 'downloads+desc' : 'downloads+desc';
+ const sort = 'downloads+desc';
try{
const url='https://archive.org/advancedsearch.php?q='+encodeURIComponent(query)+
'&fl[]=identifier&fl[]=title&fl[]=date&fl[]=venue&fl[]=coverage&fl[]=downloads&sort[]='+sort+'&rows=40&output=json';
@@ -1666,8 +1623,6 @@ function ensureGraph(){
}
let _avg=0, _clock=0, _lastBeat=-99;
function sampleLevel(){
- // stop the rAF loop when it has nothing to analyze; reactBtn / audio 'play' restart it
- if(!window.SRM.react || audio.paused){ _looping=false; return; }
if(_analyser){
_analyser.getByteFrequencyData(_freq);
let sum=0, n=Math.max(1,Math.floor(_freq.length/6)); // low-end (bass) energy
@@ -1696,9 +1651,7 @@ if(reactBtn) reactBtn.addEventListener('click',()=>{
announce(on?'Reacting to the music — lanterns, fireflies, and dancers now move to the live audio.':'Back to authored motion.');
});
-audio.addEventListener('play',()=>{ setPlay(true);
- if(window.SRM.react && _actx){ if(_actx.state==='suspended') _actx.resume(); if(!_looping){ _looping=true; sampleLevel(); } }
-});
+audio.addEventListener('play',()=>setPlay(true));
audio.addEventListener('pause',()=>setPlay(false));
audio.addEventListener('ended',()=>{ if(trackIdx<tracks.length-1) playTrack(trackIdx+1); else setPlay(false); });
audio.addEventListener('timeupdate',()=>{
@@ -1752,13 +1705,17 @@ function setYear(y){
searchArchive(searchEl?searchEl.value:'');
}
+let buildCtl=null; // cancels the previous batch of year-count fetches on a source switch
function buildMap(){
const src=window.MR_SOURCES[window.MR_SRC], mySrc=window.MR_SRC;
+ if(buildCtl) buildCtl.abort(); // kill in-flight fetches from the prior build
+ buildCtl=(typeof AbortController!=='undefined')?new AbortController():null;
+ const sig=buildCtl?buildCtl.signal:undefined;
const YEARS=[]; for(let y=src.y0;y<=src.y1;y++) YEARS.push(y);
mapEl.innerHTML='';
YEARS.forEach(y=>{
const b=document.createElement('button'); b.type='button'; b.className='mr-yr loading';
- b.dataset.y=String(y); b.title=y+' — counting…';
+ b.dataset.y=String(y); b.setAttribute('role','option'); b.title=y+' — counting…';
b.innerHTML=`<span class="yl">${y}</span><span class="yc">…</span>`;
b.addEventListener('click',()=>setYear(y));
b.addEventListener('mouseenter',()=>{ const c=counts[ck(window.MR_SRC,y)]; if(c!=null) mapLabel.textContent=`${y} · ${c.toLocaleString()} shows`; });
@@ -1766,7 +1723,7 @@ function buildMap(){
});
// per-year show counts → heat-color the tiles (sqrt to tame the '77/'89 spikes)
Promise.all(YEARS.map(y=>
- fetchT('https://archive.org/advancedsearch.php?q='+encodeURIComponent('collection:'+src.id+' AND year:'+y)+'&rows=0&output=json')
+ fetchT('https://archive.org/advancedsearch.php?q='+encodeURIComponent('collection:'+src.id+' AND year:'+y)+'&rows=0&output=json', 9000, sig)
.then(r=>r.json()).then(j=>({y,n:(j.response&&j.response.numFound)||0})).catch(()=>({y,n:-1}))
)).then(rows=>{
if(mySrc!==window.MR_SRC) return; // source switched mid-flight
@@ -1785,11 +1742,11 @@ function buildMap(){
if(srcEl){
srcEl.innerHTML=Object.entries(window.MR_SOURCES).map(([k,s])=>
- `<button type="button" data-src="${k}" class="${k===window.MR_SRC?'on':''}" aria-pressed="${k===window.MR_SRC}"><b>${s.label}</b><span class="yr">${s.years}</span></button>`).join('');
+ `<button type="button" data-src="${k}" class="${k===window.MR_SRC?'on':''}" role="tab" aria-selected="${k===window.MR_SRC}"><b>${s.label}</b><span class="yr">${s.years}</span></button>`).join('');
srcEl.addEventListener('click',e=>{
const btn=e.target.closest('button[data-src]'); if(!btn||btn.dataset.src===window.MR_SRC) return;
window.MR_SRC=btn.dataset.src;
- [...srcEl.children].forEach(c=>{ const on=c.dataset.src===window.MR_SRC; c.classList.toggle('on',on); c.setAttribute('aria-pressed',String(on)); });
+ [...srcEl.children].forEach(c=>{ const on=c.dataset.src===window.MR_SRC; c.classList.toggle('on',on); c.setAttribute('aria-selected',String(on)); });
yearEl.value=''; allBtn.classList.add('on'); mapLabel.textContent='Tap a year';
buildMap(); searchArchive(searchEl?searchEl.value:'');
});
@@ -1824,55 +1781,8 @@ mrToggle.addEventListener('keydown',e=>{ if(e.key==='Enter'||e.key===' '){ e.pre
// Music Room stays EXPANDED (incl. small screens) so revealing the chrome shows a
// working player in one tap — no hidden second collapse (CTA/screenrecord CRITICAL #1).
-// ---- Autostart on load: queue "Me and My Uncle → Bertha → Deal" and play ----
-const WISHLIST=['Me and My Uncle','Bertha','Deal'];
-function _norm(t){ return (t||'').toLowerCase().replace(/&/g,' and ').replace(/[^a-z0-9 ]+/g,' ').replace(/\bthe\b/g,' ').replace(/\s+/g,' ').trim(); }
-function _matches(title,want){ const n=_norm(title), w=_norm(want); if(!n||!w) return false;
- return new RegExp('\\b'+w.replace(/[.*+?^${}()|[\]\\]/g,'\\$&').replace(/\s+/g,'\\s+')+'\\b').test(n); }
-// fan out to the curated shows in parallel, pool every mp3 track (title + direct url)
-async function _poolFromShows(){
- const results=await Promise.allSettled(CATALOG.map(s=>
- fetch(`https://archive.org/metadata/${s.id}`,{mode:'cors'}).then(r=>r.ok?r.json():null).then(j=>({s,j}))));
- const pool=[];
- for(const res of results){
- if(res.status!=='fulfilled' || !res.value || !res.value.j) continue;
- const s=res.value.s, j=res.value.j;
- let mp3=(j.files||[]).filter(f=>/vbr mp3/i.test(f.format||'')); if(!mp3.length) mp3=(j.files||[]).filter(f=>/mp3/i.test(f.format||''));
- mp3.forEach(f=>pool.push({ title:(f.title&&f.title.trim())?f.title.trim():f.name.replace(/\.mp3$/i,''),
- url:`https://archive.org/download/${s.id}/${encodeURIComponent(f.name)}`, show:s }));
- }
- return pool;
-}
-// browsers block silent autoplay — try, and if blocked, start on the first tap/keypress
-function _tryAutoplay(){
- const go=()=>audio.play().then(()=>setPlay(true)).catch(()=>{});
- audio.play().then(()=>setPlay(true)).catch(()=>{
- const unlock=()=>{ go(); document.removeEventListener('pointerdown',unlock,true); document.removeEventListener('keydown',unlock,true); };
- document.addEventListener('pointerdown',unlock,true);
- document.addEventListener('keydown',unlock,true);
- announce('Tap or press any key to start the music — Me and My Uncle, Bertha, Deal.');
- });
-}
-async function bootMusic(){
- $('mrNow').textContent='Cueing the set…';
- try{
- const pool=await _poolFromShows();
- const list=[]; for(const w of WISHLIST){ const hit=pool.find(p=>_matches(p.title,w)); if(hit) list.push(hit); }
- if(list.length){
- tracks=list; trackIdx=0; showIdx=-1; renderTracks(); highlight();
- audio.src=tracks[0].url; if(window.SRM) window.SRM.track=tracks[0].title;
- $('mrNow').textContent=`♪ ${tracks[0].title}`;
- $('mrSub').textContent='Autostart — '+list.map(t=>t.title).join(' · ');
- setFoot(list[0].show);
- announce('Autostarting: '+list.map(t=>t.title).join(', ')+'.');
- _tryAutoplay();
- return;
- }
- }catch(e){}
- await loadShow(DEFAULT_SHOW); // fallback: archive slow/down → cue the default night, still try to start
- _tryAutoplay();
-}
-bootMusic();
+// cue the default night (no autoplay — the source policy is user-initiated playback)
+loadShow(DEFAULT_SHOW);
// reveal the DEAD back-link only when this room is served as part of the exhibition
const _p=location.pathname.replace(/\/(index\.html)?$/,'');
@@ -1902,16 +1812,8 @@ if(_p.endsWith('/room') || /dead\.agentabrams/i.test(location.hostname)){
scene.add(new THREE.AmbientLight(0x402028,0.9));
const col=new THREE.Color();
addEventListener('resize',()=>{ W=innerWidth;H=innerHeight; camera.aspect=W/H; camera.updateProjectionMatrix(); renderer.setSize(W,H,false); });
- let _orbStatic=false;
function loop(){ requestAnimationFrame(loop);
const s=window.SRM||{}, t=performance.now()/1000;
- // Honor reduced motion (WCAG 2.3.3): draw one calm static frame, then stop spinning + re-rendering.
- if(s.reduced){
- if(!_orbStatic){ group.rotation.set(Math.sin(t*0.3)*0.18,0,0); group.scale.setScalar(1);
- core.material.emissiveIntensity=0.7; renderer.render(scene,camera); _orbStatic=true; }
- return;
- }
- _orbStatic=false;
const pulse=s.pulse||0, level=(s.react?(s.level||0):0);
const idle=0.5+0.5*Math.sin(t*1.2); // gentle breathing when no music
const energy=s.react ? (level*0.6+pulse*0.7) : idle*0.12;
← 70fd187 auto-data-snapshot: 2026-09-02T09:35:20 (56 data files) — .p
·
back to Dead Agentabrams
·
auto-data-snapshot: 2026-09-02T10:09:29 (43 data files) — .p 7a653cc →