[object Object]

← back to Dead Agentabrams

room: tappable year-tile grid + Grateful Dead/Dead & Company source selector (UX-review fixes)

0300e1cb258fcbac468f5f9c4b9d1ee40983a546 · 2026-09-02 08:40:01 -0700 · Steve Abrams

Files touched

Diff

commit 0300e1cb258fcbac468f5f9c4b9d1ee40983a546
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Sep 2 08:40:01 2026 -0700

    room: tappable year-tile grid + Grateful Dead/Dead & Company source selector (UX-review fixes)
---
 room/index.html               | 468 ++++++++------------------
 scripts/screenrecord-5pass.js | 747 +++++++++++++++++++++---------------------
 2 files changed, 510 insertions(+), 705 deletions(-)

diff --git a/room/index.html b/room/index.html
index 15f41e5..fa6af65 100644
--- a/room/index.html
+++ b/room/index.html
@@ -154,18 +154,29 @@
   .mr-search{width:100%;background:rgba(40,12,16,.8);color:var(--ink);border:1px solid var(--panel-edge);border-radius:9px;padding:8px 10px;font-size:12px;margin-bottom:8px}
   .mr-search::placeholder{color:var(--ink-dim)}
   .mr-year{width:100%;background:rgba(40,12,16,.8);color:var(--ink);border:1px solid var(--panel-edge);border-radius:9px;padding:7px 9px;font-size:12px;margin-bottom:8px;cursor:pointer}
-  /* interactive touring-history year map */
-  .mr-map-head{display:flex;justify-content:space-between;align-items:center;font-size:10px;color:var(--ink-dim);margin:2px 0 3px;gap:8px}
+  /* source selector — which free Dead archive to browse */
+  .mr-src{display:flex;gap:6px;margin-bottom:9px}
+  .mr-src button{flex:1;background:rgba(40,12,16,.7);color:var(--ink-dim);border:1px solid var(--panel-edge);border-radius:9px;padding:8px 8px;cursor:pointer;line-height:1.25;text-align:center;font-family:var(--sans)}
+  .mr-src button b{display:block;font-size:12px;color:var(--ink);font-weight:600}
+  .mr-src button .yr{display:block;font-size:9px;color:var(--ink-dim);letter-spacing:.02em;margin-top:1px}
+  .mr-src button:hover{background:rgba(70,20,26,.85)}
+  .mr-src button.on{background:linear-gradient(180deg,#ffd97a,var(--gold));border-color:var(--gold)}
+  .mr-src button.on b,.mr-src button.on .yr{color:#2a0d10}
+  /* tappable touring-history year map — wrapping tile grid (WCAG-sized targets) */
+  .mr-map-head{display:flex;justify-content:space-between;align-items:center;font-size:11px;color:var(--ink-dim);margin:2px 0 5px;gap:8px}
   #mrMapLabel{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
-  .mr-allbtn{flex:none;background:rgba(70,20,26,.6);color:var(--ink);border:1px solid var(--panel-edge);border-radius:6px;font-size:10px;padding:2px 8px;cursor:pointer}
+  .mr-allbtn{flex:none;background:rgba(70,20,26,.6);color:var(--ink);border:1px solid var(--panel-edge);border-radius:8px;font-size:11px;padding:6px 12px;min-height:32px;cursor:pointer}
   .mr-allbtn.on{background:var(--gold);color:#2a0d10;border-color:var(--gold)}
-  .mr-map{display:flex;align-items:flex-end;gap:2px;height:60px;overflow-x:auto;overflow-y:hidden;padding:3px 2px 0;margin-bottom:8px;border-bottom:1px solid var(--panel-edge);scrollbar-width:thin}
-  .mr-yr{flex:0 0 auto;width:11px;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;height:100%;cursor:pointer;background:none;border:0;padding:0}
-  .mr-yr .bar{width:8px;min-height:3px;border-radius:2px 2px 0 0;background:linear-gradient(180deg,var(--gold),#7a2a1e);opacity:.5;transition:opacity .15s,box-shadow .15s}
-  .mr-yr:hover .bar{opacity:.9}
-  .mr-yr.on .bar{opacity:1;background:linear-gradient(180deg,#ffd97a,var(--gold));box-shadow:0 0 8px rgba(240,190,90,.85)}
-  .mr-yr .yl{font-size:7px;line-height:1;color:var(--ink-dim);margin-top:2px;writing-mode:vertical-rl;transform:rotate(180deg);font-variant-numeric:tabular-nums;letter-spacing:-.5px;height:14px}
-  .mr-yr.on .yl{color:var(--gold)}
+  .mr-map{display:flex;flex-wrap:wrap;gap:5px;margin-bottom:9px}
+  .mr-yr{flex:1 1 46px;min-width:46px;min-height:44px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1px;padding:5px 3px;cursor:pointer;color:var(--ink);border:1px solid var(--panel-edge);border-radius:8px;background:rgba(90,30,20,calc(.12 + var(--heat,0) * .55));transition:transform .1s,box-shadow .15s,background .15s}
+  .mr-yr:hover{transform:translateY(-1px);box-shadow:0 3px 8px rgba(0,0,0,.35)}
+  .mr-yr.on{background:linear-gradient(180deg,#ffd97a,var(--gold));border-color:var(--gold);color:#2a0d10;box-shadow:0 0 10px rgba(240,190,90,.6)}
+  .mr-yr .yl{font-size:12px;font-weight:600;line-height:1;font-variant-numeric:tabular-nums}
+  .mr-yr .yc{font-size:8.5px;color:var(--ink-dim);letter-spacing:.02em;line-height:1}
+  .mr-yr.on .yc{color:#6a2c12}
+  .mr-yr.loading{animation:mrPulse 1s ease-in-out infinite}
+  @keyframes mrPulse{0%,100%{opacity:.4}50%{opacity:.8}}
+  .mr-map-err{font-size:11px;color:#e2a08a;padding:6px 4px;font-style:italic}
   .mr-results{max-height:150px;overflow:auto;margin-bottom:8px;display:flex;flex-direction:column;gap:2px}
   .mr-results:empty{display:none}
   .mr-result{display:flex;gap:9px;align-items:baseline;width:100%;text-align:left;background:none;border:0;color:var(--ink-dim);font-family:var(--sans);font-size:11.5px;padding:5px 6px;cursor:pointer;border-radius:6px}
@@ -179,14 +190,14 @@
   .mr-now{margin:12px 0 3px;color:var(--ink);font-family:var(--serif);font-style:italic;font-size:13px;min-height:18px;
     overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
   .mr-sub{color:var(--ink-dim);font-size:10.5px;letter-spacing:.03em;margin-bottom:9px}
-  .mr-transport{display:flex;align-items:center;gap:8px;margin:6px 0}
-  .mr-transport .btn{padding:7px 10px}
-  .mr-transport .play{min-width:46px;justify-content:center;font-size:13px}
-  .mr-tracks-toggle{margin-left:auto;font-size:10px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-dim);
-    background:none;border:0;cursor:pointer;padding:4px 2px}
+  .mr-transport{display:flex;align-items:center;gap:8px;margin:8px 0}
+  .mr-transport .btn{padding:9px 12px;min-height:44px;min-width:44px;justify-content:center}
+  .mr-transport .play{min-width:54px;justify-content:center;font-size:14px}
+  .mr-tracks-toggle{margin-left:auto;font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-dim);
+    background:none;border:0;cursor:pointer;padding:10px 6px;min-height:44px}
   .mr-tracks-toggle:hover{color:var(--gold)}
-  .mr-seek{display:flex;align-items:center;margin:9px 0 4px}
-  .mr-seek input[type=range]{width:100%}
+  .mr-seek{display:flex;align-items:center;margin:6px 0 2px;padding:9px 0}
+  .mr-seek input[type=range]{width:100%;height:7px}
   .mr-time{font-variant-numeric:tabular-nums;color:var(--ink-dim);font-size:10px;text-align:right}
   .mr-tracks{max-height:148px;overflow:auto;margin-top:9px;border-top:1px solid rgba(242,182,74,.16);padding-top:8px;display:none}
   .musicroom.show-tracks .mr-tracks{display:block}
@@ -254,8 +265,9 @@
       <span class="chev" aria-hidden="true">▾</span>
     </header>
     <div class="mrbody" id="mrbody">
-      <input class="mr-search" id="mrSearch" type="search" placeholder="🔍 Search the whole Dead archive — date, city, song…" aria-label="Search the Grateful Dead archive">
-      <div class="mr-map-head"><span id="mrMapLabel">Touring history — tap a year</span><button type="button" class="mr-allbtn on" id="mrAll">All years</button></div>
+      <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="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="listbox" aria-label="Browse shows by year"></div>
       <input type="hidden" id="mrYear" value="">
       <div class="mr-results" id="mrResults" role="list"></div>
@@ -918,32 +930,6 @@ function drawBoatOverhead(accent,lan,glow){
 }
 
 // ---------- Shoreline animals ----------
-// ---- roaming / tempo helpers: animals travel across the scene and step to the beat ----
-// _tempoMul: 1.0 ≈ neutral. Detected music BPM drives it when reacting, else the Visual Tempo slider.
-function _tempoMul(){
-  if(window.SRM && window.SRM.react && window.SRM.bpm>40) return clamp(window.SRM.bpm/110, 0.7, 2.2);
-  return 0.5 + S.tempo*0.5;                      // 0.7..2.0 from the slider
-}
-// _roam: a horizontal position that walks across the screen and wraps off-screen; returns x + facing dir.
-// Derived from S.t (which already integrates tempo) so locomotion speeds up/slows with the music.
-function _roam(seed, speed){
-  const dir = (seed%2===0) ? 1 : -1;
-  const span = S.W*1.30;
-  const p = (((S.t*speed*0.06*_tempoMul()) + seed*0.31) % 1 + 1) % 1;
-  const x = dir>0 ? (-S.W*0.15 + p*span) : (S.W*1.15 - p*span);
-  return {x, dir};
-}
-// every animal gets a small upward HOP on each detected beat when reacting to the music
-function _beatHop(){ return (window.SRM && window.SRM.react) ? (S.pulse||0)*10 : 0; }
-// place a creature at its roamed position, facing its travel direction, hopping on the beat
-function _place(fn, roam, y, s, accent, hop){
-  ctx.save();
-  ctx.translate(roam.x, y - hop);
-  ctx.scale(roam.dir, 1);                         // flip to face the way it's walking/flying
-  fn(0, 0, s, S.t, accent);
-  ctx.restore();
-}
-
 function drawAnimals(){
   if(S.animals<=0.02) return;
   const W=S.W,H=S.H;
@@ -959,19 +945,13 @@ function drawAnimals(){
 
   const t=S.t;
   const scaleA = S.cam==='deck'?1.15:1;
-  const hop=_beatHop();
-
-  // walkers now ROAM the shore (each faces its direction of travel, hops on the beat)
-  _place(fox,              _roam(0,1.00), baseY-6, 1*scaleA,   accent, hop);
-  _place(heron,            _roam(3,0.55), baseY-4, 1*scaleA,   accent, hop*0.6);
-  _place(rabbit,           _roam(4,1.35), baseY-4, 1*scaleA,   accent, hop*1.4);
-  _place(bearAndFireflies, _roam(1,0.42), baseY-2, 1.1*scaleA, accent, hop*0.8);
-  if(!S.playing && !S.reduced){
-    drawIdleBackflipBear(scaleA, baseY);   // paused: one bear at a time backflipping across the page
-  } else {
-    drawBearCubs(t, scaleA, baseY);        // the troupe dances + sidesteps to tempo
-  }
-  drawSongbirds(t, scaleA, baseY);  // songbirds now FLY around the sky
+
+  fox(W*0.16, baseY-6, 1*scaleA, t, accent);
+  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);
+  drawBearCubs(t, scaleA, baseY);
+  drawSongbirds(t, scaleA, baseY);
 
   ctx.restore();
 }
@@ -1027,7 +1007,7 @@ function rabbit(x,y,s,t,accent){
   const stretch=1+air*0.4, squash=1-Math.max(0,-Math.sin(cyc))*0.35;
   const hopY=-air*9;   // low, grounded hop — stays on the shore
   ctx.save();
-  ctx.translate(x, y+hopY);   // roam is handled by _place(); keep only the vertical hop
+  ctx.translate(x + ((t*22)% (S.W*0.14)) - S.W*0.05, y+hopY);
   ctx.scale(s*(2-stretch)*0.9 + s*0.1, s*stretch*squash);
   cut('rgba(120,66,60,0.98)',2);
   ctx.beginPath(); ctx.ellipse(0,-8,9,7,0,0,7); fillOutline();
@@ -1107,42 +1087,14 @@ 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();
 }
-// idle state: when nothing is playing, ONE bear at a time backflips slowly across the page
-function drawIdleBackflipBear(scaleA, baseY){
-  const W=S.W;
-  const cross=8.5, gap=1.4, span=cross+gap;      // slow & groovy; one bear at a time
-  const tt=S.idleT||0;
-  const idx=Math.floor(tt/span);
-  const local=tt%span;
-  if(local>cross) return;                        // brief empty beat before the next bear enters
-  const prog=local/cross;                        // 0..1 travelling across the page
-  const FUR=BEAR_COLORS[idx%BEAR_COLORS.length];
-  const size=1.55*scaleA;
-  const margin=64;
-  const x=-margin + prog*(W+margin*2);           // left -> right across the page
-  const flips=3;                                 // full backflips per crossing
-  const fp=(prog*flips)%1;                        // phase within the current flip
-  const lift=Math.sin(fp*Math.PI)*54*scaleA;     // one jump-arc per flip, peaks mid-air
-  const spin=-(prog*flips)*Math.PI*2;            // backflip: rotates backward while moving right
-  const groundY=baseY+8;
-  ctx.save();
-  ctx.translate(x, groundY - 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){
   const W=S.W, N=Math.max(1,Math.min(10,Math.round(S.bears)));
   const margin=W*0.05, span=W-2*margin;
   const size=Math.max(0.9, Math.min(2.1, 13/(N+2.5)))*scaleA;   // fewer bears bigger, more bears smaller to fit
-  const mul=_tempoMul();
-  const groupDrift=Math.sin(t*0.22*mul)*W*0.05;                 // the whole troupe sways across the shore to tempo
   for(let i=0;i<N;i++){
-    const home = (N===1) ? W*0.16 : margin + span*(i/(N-1));
-    const step = Math.sin(t*2.0*mul + i*1.3)*(W*0.022+8);       // each cub sidesteps/boogies in time
-    const x = Math.max(margin*0.4, Math.min(W-margin*0.4, home + groupDrift + step));
+    const x = (N===1) ? W*0.16 : margin + span*(i/(N-1));
     const yj = ((i*53)%9)-4;                                    // slight stagger so the row isn't a flat line
     bearCub(x, baseY+8+yj, size, t, i*1.3, BEAR_COLORS[i%BEAR_COLORS.length]);
   }
@@ -1177,15 +1129,10 @@ function songbird(x,y,s,t,ph){
   }
 }
 function drawSongbirds(t,scaleA,baseY){
-  const W=S.W, H=S.H, N=5, mul=_tempoMul();   // songbirds now FLY around the sky, facing their heading
-  for(let i=0;i<N;i++){
-    const r=_roam(10+i, 0.85+i*0.14);
-    const lane=0.16+0.30*(0.5+0.5*Math.sin(i*1.7));            // spread across sky lanes
-    const y=H*lane + Math.sin(t*2.2*mul + i*1.5)*24;           // rise/dip flight-path, cadence follows tempo
-    ctx.save(); ctx.translate(r.x, y); ctx.scale(r.dir,1);
-    songbird(0, 0, (0.85+0.2*Math.sin(i))*scaleA, t, i*1.3);
-    ctx.restore();
-  }
+  const W=S.W;   // kept clear of the center control dock
+  songbird(W*0.075, baseY-9,  1.0*scaleA, t, 0.0);
+  songbird(W*0.235, baseY-7,  0.9*scaleA, t, 2.1);
+  songbird(W*0.93,  baseY-11, 1.05*scaleA, t, 4.2);
 }
 
 // ---------- River foreground with parallax reflections ----------
@@ -1288,7 +1235,6 @@ 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;   // advances even while paused, drives the idle backflip bear
 
   // live audio energy (0..1) when "React to Music" is on — otherwise motion stays authored
   const reacting = !!(window.SRM && window.SRM.react);
@@ -1300,7 +1246,7 @@ function frame(now){
 
   if(S.playing){
     const spd = S.reduced ? 0.28 : 1.0;
-    S.t += dt * S.tempo * spd * 0.85;   // slowed — jazzy/groovy pacing
+    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){
@@ -1517,25 +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:'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');
@@ -1599,8 +1533,9 @@ async function searchArchive(q){
   const res=$('mrResults'); if(!res) return;
   q=(q||'').trim();
   const yr=($('mrYear')&&$('mrYear').value)||'';
+  const coll='collection:'+((window.MR_SOURCES&&window.MR_SOURCES[window.MR_SRC||'gd'])||{id:'GratefulDead'}).id;
   const filters=[]; if(q) filters.push('('+q+')'); if(yr) filters.push('year:'+yr);
-  const query = filters.length ? 'collection:GratefulDead AND '+filters.join(' AND ') : 'collection:GratefulDead';
+  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';
   try{
@@ -1662,18 +1597,7 @@ function setPlay(on){
 // Default OFF — visuals stay authored until the visitor flips "React to Music".
 audio.crossOrigin='anonymous';                 // set before any src load so the CDN stream is analyzable
 window.SRM = window.SRM || { level:0, react:false };
-// song-analysis fields the orb + animals read: bpm (detected tempo), vocal (0..1 lyric presence),
-// change (a counter that ticks on a NOTICEABLE song change), changeKind ('track'|'tempo'|'vocal-in'|'vocal-out')
-Object.assign(window.SRM,{ bpm:0, vocal:0, change:window.SRM.change||0, changeKind:'', changeAt:0 });
 let _actx, _analyser, _freq, _looping=false;
-// beat-interval ring buffer -> BPM, plus running baselines for change detection
-let _beatTimes=[], _bpmBase=0, _vocalSm=0, _vocalState=false, _lastTrack='', _lastChangeClock=-999;
-function _median(a){ const b=a.slice().sort((x,y)=>x-y); return b.length? b[(b.length>>1)] : 0; }
-function _fireChange(kind){
-  if(_clock-_lastChangeClock < 90) return;   // rate-limit: ~1.5s between change events
-  _lastChangeClock=_clock;
-  window.SRM.change++; window.SRM.changeKind=kind; window.SRM.changeAt=performance.now();
-}
 function ensureGraph(){
   if(_actx) return true;
   try{
@@ -1689,43 +1613,16 @@ let _avg=0, _clock=0, _lastBeat=-99;
 function sampleLevel(){
   if(_analyser){
     _analyser.getByteFrequencyData(_freq);
-    const L=_freq.length;                                     // 128 bins, ~0..22kHz
-    let sum=0, n=Math.max(1,Math.floor(L/6));                 // low-end (bass) energy
+    let sum=0, n=Math.max(1,Math.floor(_freq.length/6));  // low-end (bass) energy
     for(let i=0;i<n;i++) sum+=_freq[i];
     const raw=(sum/n)/255;
-    window.SRM.level = window.SRM.level*0.6 + raw*0.4;        // smoothed 0..1
-    // vocal-band (~300Hz..3.4kHz -> mid bins) energy, normalised against overall energy:
-    // a strong, sustained mid presence over the bass floor reads as "lyrics / a voice is in"
-    let vsum=0, vn=0, tsum=0;
-    for(let i=3;i<24 && i<L;i++){ vsum+=_freq[i]; vn++; }
-    for(let i=0;i<L;i++) tsum+=_freq[i];
-    const vocalRaw = tsum>1 ? Math.min(1,(vsum/Math.max(1,vn))/ (tsum/L) * 0.5) : 0;
-    _vocalSm = _vocalSm*0.9 + vocalRaw*0.1;
-    window.SRM.vocal = _vocalSm;
+    window.SRM.level = window.SRM.level*0.6 + raw*0.4;       // smoothed 0..1
     // beat detection: a bass spike well above the running average, rate-limited
     _avg = _avg*0.92 + raw*0.08; _clock++;
     if(raw > _avg*1.10 && raw > 0.12 && (_clock-_lastBeat) > 5){
-      const now=performance.now();
-      if(window.SRM.beat){ const dtb=now-(window.SRM._lastBeatMs||now); if(dtb>240 && dtb<1400){ _beatTimes.push(dtb); if(_beatTimes.length>8) _beatTimes.shift(); } }
-      window.SRM._lastBeatMs=now;
       _lastBeat=_clock; window.SRM.beat=(window.SRM.beat||0)+1; window.SRM.pulse=1;
-      // BPM from the median beat interval, smoothed
-      if(_beatTimes.length>=4){ const bpm=60000/_median(_beatTimes); window.SRM.bpm = window.SRM.bpm? window.SRM.bpm*0.8+bpm*0.2 : bpm; }
-    }
-    window.SRM.pulse = (window.SRM.pulse||0)*0.86;            // decaying throb (spikes to 1 on a beat)
-
-    // ---- NOTICEABLE-CHANGE detection (drives the orb's UFO-split & celestial morphs) ----
-    // 1) a new track started
-    const tr=window.SRM.track||'';
-    if(tr && tr!==_lastTrack){ _lastTrack=tr; _bpmBase=0; _beatTimes=[]; _fireChange('track'); }
-    // 2) a real tempo shift vs the running baseline
-    if(window.SRM.bpm>40){
-      if(!_bpmBase) _bpmBase=window.SRM.bpm;
-      else { if(Math.abs(window.SRM.bpm-_bpmBase)/_bpmBase > 0.18) _fireChange('tempo'); _bpmBase=_bpmBase*0.98+window.SRM.bpm*0.02; }
     }
-    // 3) lyrics-vs-just-music crossover (hysteresis so it doesn't flicker)
-    if(!_vocalState && _vocalSm>0.42){ _vocalState=true; _fireChange('vocal-in'); }
-    else if(_vocalState && _vocalSm<0.26){ _vocalState=false; _fireChange('vocal-out'); }
+    window.SRM.pulse = (window.SRM.pulse||0)*0.86;           // decaying throb (spikes to 1 on a beat)
   }
   requestAnimationFrame(sampleLevel);
 }
@@ -1774,47 +1671,81 @@ seek.addEventListener('change',()=>{ const d=audio.duration||0; if(d>0) audio.cu
 
 sel.addEventListener('change',()=>loadShow(+sel.value,{play:true}));
 
-// archive search wiring — type anything, debounced; Enter searches immediately
+// archive browse wiring — source selector + tappable year-tile grid + debounced search
 const searchEl=$('mrSearch'), yearEl=$('mrYear');
-// Interactive touring-history map: one bar per year, height ∝ shows surviving that year.
-const mapEl=$('mrMap'), mapLabel=$('mrMapLabel'), allBtn=$('mrAll');
-if(mapEl && yearEl){
-  const YEARS=[]; for(let y=1965;y<=1995;y++) YEARS.push(y);
-  const counts={};
-  function setYear(y){
-    yearEl.value=y?String(y):'';
-    [...mapEl.children].forEach(c=>c.classList.toggle('on', c.dataset.y===String(y)));
-    allBtn.classList.toggle('on', !y);
-    mapLabel.textContent = y ? `${y} · ${(counts[y]||0).toLocaleString()} shows` : 'Touring history — tap a year';
-    searchArchive(searchEl?searchEl.value:'');
-  }
+const mapEl=$('mrMap'), mapLabel=$('mrMapLabel'), allBtn=$('mrAll'), srcEl=$('mrSrc');
+
+// Two FREE, band-sanctioned Dead archives on archive.org (CORS-open, streamable).
+window.MR_SOURCES={
+  gd:{id:'GratefulDead',   label:'Grateful Dead', years:'1965–1995', y0:1965, y1:1995},
+  dc:{id:'DeadAndCompany', label:'Dead & Company', years:'2015–2023', y0:2015, y1:2023}
+};
+window.MR_SRC='gd';
+const counts={};                              // per (source|year) show-count cache
+const ck=(s,y)=>s+':'+y;
+
+function setYear(y){
+  yearEl.value=y?String(y):'';
+  [...mapEl.children].forEach(c=>c.classList&&c.classList.toggle('on', c.dataset.y===String(y)));
+  allBtn.classList.toggle('on', !y);
+  const c=counts[ck(window.MR_SRC,y)];
+  mapLabel.textContent = y ? `${y}${c!=null?' · '+c.toLocaleString()+' shows':''}` : 'Tap a year';
+  searchArchive(searchEl?searchEl.value:'');
+}
+
+function buildMap(){
+  const src=window.MR_SOURCES[window.MR_SRC], mySrc=window.MR_SRC;
+  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'; b.dataset.y=String(y); b.setAttribute('role','option');
-    b.title=y+' — counting…';
-    b.innerHTML=`<span class="bar" style="height:3px"></span><span class="yl">${y%5===0?y:''}</span>`;
+    const b=document.createElement('button'); b.type='button'; b.className='mr-yr loading';
+    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',()=>{ if(counts[y]!=null) mapLabel.textContent=`${y} · ${counts[y].toLocaleString()} shows`; });
+    b.addEventListener('mouseenter',()=>{ const c=counts[ck(window.MR_SRC,y)]; if(c!=null) mapLabel.textContent=`${y} · ${c.toLocaleString()} shows`; });
     mapEl.appendChild(b);
   });
-  mapEl.addEventListener('mouseleave',()=>{ const on=yearEl.value; mapLabel.textContent = on?`${on} · ${(counts[on]||0).toLocaleString()} shows`:'Touring history — tap a year'; });
-  allBtn.addEventListener('click',()=>setYear(0));
-  // Fetch per-year show counts in parallel, then scale bars (sqrt to tame the '77–'90 spikes).
+  // per-year show counts → heat-color the tiles (sqrt to tame the '77/'89 spikes)
   Promise.all(YEARS.map(y=>
-    fetch('https://archive.org/advancedsearch.php?q='+encodeURIComponent('collection:GratefulDead AND year:'+y)+'&rows=0&output=json',{mode:'cors'})
-      .then(r=>r.json()).then(j=>({y,n:(j.response&&j.response.numFound)||0})).catch(()=>({y,n:0}))
+    fetch('https://archive.org/advancedsearch.php?q='+encodeURIComponent('collection:'+src.id+' AND year:'+y)+'&rows=0&output=json',{mode:'cors'})
+      .then(r=>r.json()).then(j=>({y,n:(j.response&&j.response.numFound)||0})).catch(()=>({y,n:-1}))
   )).then(rows=>{
-    const max=Math.max(1,...rows.map(r=>Math.sqrt(r.n)));
-    rows.forEach(({y,n})=>{ counts[y]=n; const el=mapEl.querySelector('[data-y="'+y+'"]'); if(!el) return;
-      el.querySelector('.bar').style.height=Math.round(3+(Math.sqrt(n)/max)*40)+'px';
-      el.title=y+' — '+n.toLocaleString()+' shows';
+    if(mySrc!==window.MR_SRC) return;                          // source switched mid-flight
+    if(!rows.some(r=>r.n>=0)){ mapEl.innerHTML='<div class="mr-map-err">Couldn’t load the tour history — archive.org may be unreachable. Search still works.</div>'; return; }
+    const max=Math.max(1,...rows.filter(r=>r.n>0).map(r=>Math.sqrt(r.n)));
+    rows.forEach(({y,n})=>{ const el=mapEl.querySelector('[data-y="'+y+'"]'); if(!el) return;
+      el.classList.remove('loading'); const nn=Math.max(0,n); counts[ck(mySrc,y)]=nn;
+      el.style.setProperty('--heat',(Math.sqrt(nn)/max).toFixed(3));
+      el.querySelector('.yc').textContent=nn.toLocaleString();
+      el.title=y+' — '+nn.toLocaleString()+' shows';
     });
-    const cur=yearEl.value; if(cur) mapLabel.textContent=`${cur} · ${(counts[cur]||0).toLocaleString()} shows`;
-  }).catch(()=>{});
+    const cur=yearEl.value, c=cur?counts[ck(mySrc,cur)]:null;
+    if(cur&&c!=null) mapLabel.textContent=`${cur} · ${c.toLocaleString()} shows`;
+  });
+}
+
+if(srcEl){
+  srcEl.innerHTML=Object.entries(window.MR_SOURCES).map(([k,s])=>
+    `<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-selected',String(on)); });
+    yearEl.value=''; allBtn.classList.add('on'); mapLabel.textContent='Tap a year';
+    buildMap(); searchArchive(searchEl?searchEl.value:'');
+  });
 }
+
+if(mapEl && yearEl){
+  mapEl.addEventListener('mouseleave',()=>{ const on=yearEl.value, c=on?counts[ck(window.MR_SRC,on)]:null; mapLabel.textContent = on?`${on}${c!=null?' · '+c.toLocaleString()+' shows':''}`:'Tap a year'; });
+  allBtn.addEventListener('click',()=>setYear(0));
+  buildMap();
+}
+
 if(searchEl){
   searchEl.addEventListener('input',()=>{ clearTimeout(searchTimer); searchTimer=setTimeout(()=>searchArchive(searchEl.value),450); });
   searchEl.addEventListener('keydown',e=>{ if(e.key==='Enter'){ e.preventDefault(); clearTimeout(searchTimer); searchArchive(searchEl.value); } });
-  searchArchive('');   // populate the most-played shows from the whole archive on load
+  searchArchive('');   // populate the most-played shows on load
 }
 
 $('mrTracksToggle').addEventListener('click',()=>{
@@ -1831,10 +1762,8 @@ function toggleRoom(){
 mrToggle.addEventListener('click',toggleRoom);
 mrToggle.addEventListener('keydown',e=>{ if(e.key==='Enter'||e.key===' '){ e.preventDefault(); toggleRoom(); } });
 
-// small screens: start collapsed so the art leads
-if(window.matchMedia && matchMedia('(max-width:820px)').matches){
-  room.setAttribute('data-open','false'); mrToggle.setAttribute('aria-expanded','false');
-}
+// 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).
 
 // cue the default night (no autoplay — the source policy is user-initiated playback)
 loadShow(DEFAULT_SHOW);
@@ -1859,163 +1788,28 @@ if(_p.endsWith('/room') || /dead\.agentabrams/i.test(location.hostname)){
   const scene=new THREE.Scene();
   const camera=new THREE.PerspectiveCamera(50,W/H,0.1,100); camera.position.z=5;
   const group=new THREE.Group(); group.position.y=1.45; scene.add(group);   // floats up in the sky over the boat
-
-  // ---- the core orb (its own sub-group so effects can rotate it WITHOUT tilting the world-down beams) ----
-  const orb=new THREE.Group(); group.add(orb);
   const wire=new THREE.Mesh(new THREE.IcosahedronGeometry(0.95,2), new THREE.MeshBasicMaterial({wireframe:true,color:0xf2b64a,transparent:true,opacity:0.8}));
   const core=new THREE.Mesh(new THREE.IcosahedronGeometry(0.76,3), new THREE.MeshStandardMaterial({color:0x2a0810,emissive:0xc9432b,emissiveIntensity:0.7,roughness:0.35,metalness:0.6,transparent:true,opacity:0.85}));
-  orb.add(core,wire);
-
-  // ---- 5 UFO saucers, each casting a downward light beam (searchlight cone, additive) ----
-  const ufo=new THREE.Group(); ufo.visible=false; group.add(ufo);
-  const saucers=[];
-  for(let i=0;i<5;i++){
-    const u=new THREE.Group();
-    const body=new THREE.Mesh(new THREE.SphereGeometry(0.24,20,12), new THREE.MeshStandardMaterial({color:0x0d1220,emissive:0x24406e,emissiveIntensity:0.7,roughness:0.25,metalness:0.85}));
-    body.scale.set(1,0.32,1);
-    const dome=new THREE.Mesh(new THREE.SphereGeometry(0.13,16,10,0,Math.PI*2,0,Math.PI/2), new THREE.MeshStandardMaterial({color:0xbdf0ff,emissive:0x46c4ff,emissiveIntensity:1.4,roughness:0.15,metalness:0.4,transparent:true,opacity:0.9}));
-    dome.position.y=0.055;
-    const rim=new THREE.Mesh(new THREE.TorusGeometry(0.215,0.028,8,26), new THREE.MeshBasicMaterial({color:0x9fe8ff,transparent:true,opacity:0.9}));
-    rim.rotation.x=Math.PI/2;
-    // searchlight: cone apex at the saucer (top), wide base sweeping DOWN to the boat
-    const beam=new THREE.Mesh(new THREE.ConeGeometry(0.42,1.7,22,1,true), new THREE.MeshBasicMaterial({color:0x8fe0ff,transparent:true,opacity:0.14,side:THREE.DoubleSide,depthWrite:false,blending:THREE.AdditiveBlending}));
-    beam.position.y=-0.9;                    // hangs below the saucer
-    u.add(beam,body,rim,dome);
-    ufo.add(u); saucers.push({g:u,beam,dome,rim,ang:i/5*Math.PI*2,spin:0.4+i*0.12});
-  }
-
-  // ---- celestial patterns: a morphing point-cloud + constellation lines ----
-  const CN=224;
-  const cur=new Float32Array(CN*3), targ=new Float32Array(CN*3);
-  const pgeo=new THREE.BufferGeometry(); pgeo.setAttribute('position',new THREE.BufferAttribute(cur,3));
-  const stars=new THREE.Points(pgeo,new THREE.PointsMaterial({color:0xfff1cf,size:0.055,transparent:true,opacity:0.95,depthWrite:false,blending:THREE.AdditiveBlending}));
-  const MAXSEG=110, lpos=new Float32Array(MAXSEG*2*3), lpair=[];
-  const lgeo=new THREE.BufferGeometry(); lgeo.setAttribute('position',new THREE.BufferAttribute(lpos,3));
-  const lines=new THREE.LineSegments(lgeo,new THREE.LineBasicMaterial({color:0x9fd0ff,transparent:true,opacity:0.28,depthWrite:false,blending:THREE.AdditiveBlending}));
-  const celest=new THREE.Group(); celest.visible=false; celest.add(stars,lines); group.add(celest);
-  // pattern builders write into targ[] and rebuild the constellation line pairs
-  const PATTERNS=['galaxy','orbits','shell','starburst'];
-  function buildPattern(name){
-    for(let i=0;i<CN;i++){
-      let x=0,y=0,z=0; const f=i/CN;
-      if(name==='galaxy'){ const arm=i%3, a=f*7.5+arm*2.094, r=0.18+f*1.35; x=Math.cos(a)*r; y=Math.sin(a)*r*0.55; z=Math.sin(i*0.7)*0.14; }
-      else if(name==='orbits'){ const ring=i%5, a=(i/5)*0.9+ring*1.3, r=0.5+ring*0.22, tilt=ring*0.5; x=Math.cos(a)*r; y=Math.sin(a)*r*Math.cos(tilt); z=Math.sin(a)*r*Math.sin(tilt); }
-      else if(name==='shell'){ const gr=(1+Math.sqrt(5))/2, th=2*Math.PI*i/gr, ph=Math.acos(1-2*(i+0.5)/CN), r=1.25; x=Math.sin(ph)*Math.cos(th)*r; y=Math.cos(ph)*r; z=Math.sin(ph)*Math.sin(th)*r; }
-      else { const pt=Math.floor(f*10), sub=(f*10)%1, base=(pt%2?0.45:1.3), a=pt*Math.PI/5; const r=base*(1-sub*0.15); x=Math.cos(a)*r; y=Math.sin(a)*r; z=(Math.random()-0.5)*0.18; } // starburst
-      targ[i*3]=x; targ[i*3+1]=y; targ[i*3+2]=z;
-    }
-    // nearest-neighbour constellation edges (from the target shape)
-    lpair.length=0;
-    for(let i=0;i<CN && lpair.length<MAXSEG;i++){
-      let bj=-1,bd=1e9;
-      for(let j=i+1;j<CN;j++){ const dx=targ[i*3]-targ[j*3],dy=targ[i*3+1]-targ[j*3+1],dz=targ[i*3+2]-targ[j*3+2],d=dx*dx+dy*dy+dz*dz; if(d<bd){bd=d;bj=j;} }
-      if(bj>=0 && bd<0.16) lpair.push([i,bj]);
-    }
-  }
-  let patIdx=0; buildPattern(PATTERNS[0]);
-  for(let i=0;i<CN*3;i++) cur[i]=targ[i]*0.2;   // start collapsed toward the core
-  function cyclePattern(){ patIdx=(patIdx+1)%PATTERNS.length; buildPattern(PATTERNS[patIdx]); }
-
+  group.add(core,wire);
   const l1=new THREE.PointLight(0xff8a4a,1.3,60); l1.position.set(4,3,5); scene.add(l1);
   const l2=new THREE.PointLight(0xf2b64a,1.0,60); l2.position.set(-4,-1,4); scene.add(l2);
-  const l3=new THREE.PointLight(0x7fd0ff,0.0,60); l3.position.set(0,3,4); scene.add(l3);   // cool light, rises in UFO/celestial modes
   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); });
-
-  // ---- effect state machine: 'none' (orb), 'ufo', 'celestial', 'bloom' ----
-  let fx='none', fxT=0, fxDur=7, w=0, seenChange=(window.SRM&&window.SRM.change)||0;
-  let ai=false, quietT=0;
-  function enterFx(name,dur){ fx=name; fxT=0; fxDur=dur||7; if(name==='celestial') cyclePattern(); }
-  // AI VJ picks the next effect from the live audio; when off, a noticeable change still splits the orb
-  function director(kind,level,vocal,bpm){
-    if(ai){
-      if(level>0.55 || bpm>132) enterFx('ufo',6);
-      else if(vocal<0.24 || kind==='vocal-out') enterFx('celestial',9);
-      else enterFx(Math.random()<0.55?'ufo':'bloom', 5);
-    } else {
-      if(kind==='vocal-out') enterFx('celestial',8);   // instrumental stretch -> stars
-      else enterFx('ufo',7);                            // any other noticeable change -> 5 UFOs
-    }
-  }
-
-  // click the orb to hand it to the AI VJ (autonomous, reads the live audio and choreographs itself)
-  function orbScreen(){ const v=group.position.clone().project(camera); return {x:(v.x*0.5+0.5)*W, y:(-v.y*0.5+0.5)*H, r:Math.max(60,Math.min(W,H)*0.16)}; }
-  addEventListener('pointerdown', e=>{
-    const o=orbScreen();
-    if(Math.hypot(e.clientX-o.x, e.clientY-o.y) < o.r){
-      ai=!ai; e.stopPropagation();
-      if(ai){ enterFx('celestial',9); if(window.SRM) window.SRM.aiVJ=true; }
-      else { fx='none'; if(window.SRM) window.SRM.aiVJ=false; }
-      const lv=document.getElementById('live'); if(lv) lv.textContent = ai ? 'The orb is taken over by the AI VJ — it choreographs UFOs, celestial patterns and blooms to the music.' : 'AI VJ released. The orb responds to song changes on its own.';
-    }
-  }, true);
-
-  const easeOut=x=>1-Math.pow(1-x,3);
-  let last=performance.now();
-  function loop(now){ requestAnimationFrame(loop);
-    const dt=Math.min(0.05,((now||performance.now())-last)/1000); last=now||performance.now();
-    const s=window.SRM||{}, t=(now||performance.now())/1000;
-    const pulse=s.pulse||0, level=(s.react?(s.level||0):0), vocal=(s.react?(s.vocal||0):0), bpm=s.bpm||0;
-    const idle=0.5+0.5*Math.sin(t*1.2);
+  function loop(){ requestAnimationFrame(loop);
+    const s=window.SRM||{}, t=performance.now()/1000;
+    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;
-
-    // fire effects on a NOTICEABLE song change; the AI VJ also self-triggers on quiet stretches
-    const chg=(s.change||0);
-    if(chg>seenChange){ seenChange=chg; director(s.changeKind||'', level, vocal, bpm); }
-    if(s.react && level<0.16 && vocal<0.18){ quietT+=dt; if(quietT>9 && fx==='none'){ enterFx('celestial', ai?10:7); quietT=0; } } else quietT=0;
-
-    // blend weight w: ramp up while the effect holds, ramp back down, then release to the plain orb
-    const active = fx!=='none' && fxT<fxDur;
-    if(fx!=='none'){ fxT+=dt; if(fxT>=fxDur+0.7 && w<0.02){ fx='none'; } }
-    w += ((active?1:0)-w)*Math.min(1,dt*4.5);
-    const ew=easeOut(Math.max(0,Math.min(1,w)));
-
-    // ---- the plain orb (always present; shrinks away as an effect takes hold) ----
-    orb.visible = w<0.995;
-    orb.rotation.y+=0.008+energy*0.02; orb.rotation.x=Math.sin(t*0.3)*0.18;
-    const baseScale=(1+energy*0.35)*(fx==='bloom'? (1+ew*(0.8+pulse*1.4)) : (1-ew));
-    orb.scale.setScalar(Math.max(0.0001,baseScale));
-    core.material.emissiveIntensity=0.5+energy*1.6 + (fx==='bloom'?ew*2.5:0);
-    const hue=(28 + Math.sin(t*0.2)*16 + level*40)%360;
+    group.rotation.y+=0.008+energy*0.02; group.rotation.x=Math.sin(t*0.3)*0.18;
+    group.scale.setScalar(1+energy*0.35);
+    core.material.emissiveIntensity=0.5+energy*1.6;
+    const hue=(28 + Math.sin(t*0.2)*16 + level*40)%360;  // warm ember range, shifts with the music
     wire.material.color.setHSL(((hue+18)%360)/360,0.85,0.6);
     col.setHSL(hue/360,0.85,0.5); core.material.emissive.copy(col);
-    l3.intensity = ew*1.4;
-
-    // ---- UFO formation ----
-    ufo.visible = (fx==='ufo') && w>0.002;
-    if(ufo.visible){
-      const spread=0.35+ew*0.95, hover=1.6;   // saucers hover above the boat then beam down
-      for(let i=0;i<saucers.length;i++){ const S5=saucers[i];
-        const a=S5.ang + t*0.25;
-        S5.g.position.set(Math.cos(a)*spread, (i-2)*0.06 + Math.sin(t*1.3+i)*0.08, Math.sin(a)*spread*0.65);
-        S5.g.rotation.y += (S5.spin+pulse*2.0)*dt*2.2;
-        S5.g.scale.setScalar(ew);
-        S5.beam.material.opacity = (0.10+pulse*0.22+level*0.14)*ew;   // beams throb on the beat
-        S5.beam.scale.y = 1+pulse*0.5 + Math.sin(t*3+i)*0.08;
-        const bh=(200+ i*26 + (bpm||0))%360; S5.dome.material.emissive.setHSL(bh/360,0.9,0.6);
-      }
-    }
-
-    // ---- celestial point-cloud ----
-    celest.visible = (fx==='celestial') && w>0.002;
-    if(celest.visible){
-      for(let i=0;i<CN*3;i++) cur[i]+=(targ[i]-cur[i])*Math.min(1,dt*2.6);
-      pgeo.attributes.position.needsUpdate=true;
-      for(let k=0;k<lpair.length;k++){ const a=lpair[k][0]*3,b=lpair[k][1]*3,o=k*6;
-        lpos[o]=cur[a];lpos[o+1]=cur[a+1];lpos[o+2]=cur[a+2]; lpos[o+3]=cur[b];lpos[o+4]=cur[b+1];lpos[o+5]=cur[b+2]; }
-      lgeo.setDrawRange(0,lpair.length*2); lgeo.attributes.position.needsUpdate=true;
-      celest.rotation.y += (0.12+energy*0.4)*dt; celest.rotation.x=Math.sin(t*0.2)*0.25;
-      celest.scale.setScalar(ew*(1+pulse*0.12));
-      stars.material.opacity=0.7+0.3*Math.sin(t*4)+pulse*0.3;
-      stars.material.color.setHSL(((t*10+level*60)%360)/360,0.5,0.85);
-      // re-morph to the next celestial pattern on a strong beat, a few seconds in
-      if(fxT>3 && pulse>0.85 && Math.random()<0.04) cyclePattern();
-    }
-
     renderer.render(scene,camera);
   }
-  requestAnimationFrame(loop);
+  loop();
 })();
 </script>
 </body>
diff --git a/scripts/screenrecord-5pass.js b/scripts/screenrecord-5pass.js
index af8493a..660a73b 100644
--- a/scripts/screenrecord-5pass.js
+++ b/scripts/screenrecord-5pass.js
@@ -1,38 +1,33 @@
 'use strict';
 // 5-pass screen recording + click-every-element debug agent
 // dead.agentabrams.com/room/ — Grateful Dead music room
-// Skill: screenrecord/SKILL.md
-// Usage: NODE_PATH=$HOME/.npm-global/lib/node_modules node scripts/screenrecord-5pass.js
+// NODE_PATH=$HOME/.npm-global/lib/node_modules node scripts/screenrecord-5pass.js
 
 const { chromium } = require('playwright');
 const fs   = require('fs');
 const path = require('path');
+const { execSync } = require('child_process');
 
 const URL        = 'https://dead.agentabrams.com/room/';
 const CHROME_EXE = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
 const LOG        = '/Users/macstudio3/Projects/dead-agentabrams/screenrecord/debug-log.jsonl';
 const REC_BASE   = '/Users/macstudio3/Projects/dead-agentabrams/screenrecord/rec';
 
-// Ensure dirs exist
 fs.mkdirSync(path.dirname(LOG), { recursive: true });
 for (let r = 0; r < 5; r++) fs.mkdirSync(path.join(REC_BASE, `run${r}`), { recursive: true });
 
-// Load prior log for run4 "errors-first" strategy
 const prior = fs.existsSync(LOG)
   ? fs.readFileSync(LOG, 'utf8').trim().split('\n').filter(Boolean).map(l => {
       try { return JSON.parse(l); } catch { return null; }
     }).filter(Boolean)
   : [];
 const priorErrors = new Set(prior.filter(r => r.errors && r.errors.length > 0).map(r => r.selector));
-
 console.log(`Prior log entries: ${prior.length}, prior errored selectors: ${priorErrors.size}`);
 
-// Append one JSONL line
 function append(obj) {
   fs.appendFileSync(LOG, JSON.stringify(obj) + '\n');
 }
 
-// Deterministic seeded shuffle (run3)
 function seededShuffle(arr, seed) {
   const s = [...arr];
   let state = seed;
@@ -44,35 +39,19 @@ function seededShuffle(arr, seed) {
   return s;
 }
 
-// Build click order for each run
 function orderFor(run, els) {
-  const byType = t => t === 'range';
   switch(run) {
-    case 0: return [...els]; // DOM order
-    case 1: return [...els].reverse(); // reverse DOM
-    case 2: return [...els].sort((a, b) => byType(b.type) - byType(a.type)); // sliders first
-    case 3: return seededShuffle(els, 42 * 13 + run * 7); // seeded shuffle
+    case 0: return [...els];
+    case 1: return [...els].reverse();
+    case 2: return [...els].sort((a, b) => (b.type === 'range' ? 1 : 0) - (a.type === 'range' ? 1 : 0));
+    case 3: return seededShuffle(els, 42 * 13 + run * 7);
     case 4: {
-      // errors-first: put previously errored selectors at front
       const bad = sel => priorErrors.has(sel) ? 0 : 1;
       return [...els].sort((a, b) => bad(a.selector) - bad(b.selector));
     }
   }
 }
 
-// Measure how long archive.org fetch takes (31 parallel count queries)
-async function measureBarHeightTiming(page) {
-  return await page.evaluate(() => {
-    return new Promise(resolve => {
-      const bars = Array.from(document.querySelectorAll('.mr-yr .bar, .bar'));
-      if (!bars.length) { resolve({ barCount: 0, hasRealHeights: false }); return; }
-      // Check if bars have data-loaded or non-zero height from parent
-      const parentHeights = Array.from(document.querySelectorAll('.mr-yr')).map(b => b.style.height || window.getComputedStyle(b).height);
-      resolve({ barCount: bars.length, parentHeights: parentHeights.slice(0, 5) });
-    });
-  });
-}
-
 async function runPass(runIdx) {
   console.log(`\n===== RUN ${runIdx} =====`);
   const recDir = path.join(REC_BASE, `run${runIdx}`);
@@ -80,297 +59,285 @@ async function runPass(runIdx) {
   const browser = await chromium.launch({
     executablePath: CHROME_EXE,
     headless: true,
-    args: ['--no-sandbox', '--disable-web-security']
+    args: ['--no-sandbox']
   });
 
   const ctx = await browser.newContext({
     viewport: { width: 1600, height: 900 },
-    recordVideo: { dir: recDir, size: { width: 1600, height: 900 } },
-    userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
+    recordVideo: { dir: recDir, size: { width: 1600, height: 900 } }
   });
 
   const page = await ctx.newPage();
   const consoleErrors = [];
   const pageErrors = [];
+  const archiveRequests = [];
+  const archiveErrors = [];
+
   page.on('console', m => {
-    if (m.type() === 'error') consoleErrors.push(m.text().slice(0, 300));
+    const txt = m.text().slice(0, 300);
+    if (m.type() === 'error') consoleErrors.push(txt);
+    if (txt.includes('archive') || txt.includes('fetch') || txt.includes('Error')) {
+      consoleErrors.push(`[console:${m.type()}] ${txt}`);
+    }
   });
   page.on('pageerror', e => pageErrors.push(('' + e).slice(0, 300)));
-
-  // Track network requests to archive.org
-  const archiveRequests = [];
-  const archiveErrors = [];
-  page.on('requestfinished', async req => {
+  page.on('requestfailed', req => {
     if (req.url().includes('archive.org')) {
-      try {
-        const resp = await req.response();
-        archiveRequests.push({ url: req.url().slice(0, 100), status: resp ? resp.status() : null });
-      } catch {
-        archiveRequests.push({ url: req.url().slice(0, 100), status: 'err' });
-      }
+      archiveErrors.push({ url: req.url().slice(0, 120), err: req.failure()?.errorText });
     }
   });
-  page.on('requestfailed', req => {
-    if (req.url().includes('archive.org')) archiveErrors.push({ url: req.url().slice(0, 100), err: req.failure()?.errorText });
+  page.on('response', resp => {
+    if (resp.url().includes('archive.org')) {
+      archiveRequests.push({ url: resp.url().slice(0, 120), status: resp.status() });
+    }
   });
 
   const loadStart = Date.now();
   await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 30000 });
   const domReadyMs = Date.now() - loadStart;
+  await page.waitForTimeout(2000);
 
-  // Wait for initial JS execution
-  await page.waitForTimeout(3000);
-  const postInitMs = Date.now() - loadStart;
-
-  // Check bars immediately after load (before archive.org fills heights)
+  // Capture bar heights at load (BEFORE archive.org fills them)
   const barsAtLoad = await page.evaluate(() => {
     const bars = Array.from(document.querySelectorAll('.mr-yr'));
-    return bars.map(b => ({ style: b.style.height || 'none', computed: window.getComputedStyle(b).height })).slice(0, 3);
+    return {
+      count: bars.length,
+      sampleStyles: bars.slice(0, 3).map(b => ({ style: b.style.height, computed: window.getComputedStyle(b).height }))
+    };
   });
 
-  append({
-    run: runIdx, ts: new Date().toISOString(), selector: 'PAGE_LOAD', label: 'initial load',
+  append({ run: runIdx, ts: new Date().toISOString(), selector: 'PAGE_LOAD', label: 'page load',
     action: 'navigate', ok: true,
-    effect: `domReady=${domReadyMs}ms postInit=${postInitMs}ms barsAtLoad=${JSON.stringify(barsAtLoad)}`,
-    errors: [...consoleErrors.splice(0), ...pageErrors.splice(0)]
-  });
+    effect: `domReady=${domReadyMs}ms barsAtLoad=${JSON.stringify(barsAtLoad)}`,
+    errors: [...consoleErrors.splice(0), ...pageErrors.splice(0)] });
 
-  // ---- Step 1: Click hamburger to reveal panel stack ----
-  const beforeHamburger = await page.screenshot({ clip: { x: 1400, y: 0, width: 200, height: 80 } });
+  // ---- Step 1: Open hamburger ----
   let hamburgerOk = false;
   try {
-    await page.click('#chromeBtn', { timeout: 5000 });
+    await page.click('#chromeBtn', { force: true, timeout: 5000 });
     await page.waitForTimeout(800);
     hamburgerOk = true;
-  } catch (e) {
-    consoleErrors.push('hamburger click failed: ' + e.message.slice(0, 100));
+  } catch(e) {
+    append({ run: runIdx, ts: new Date().toISOString(), selector: '#chromeBtn', label: 'hamburger',
+      action: 'click', ok: false, effect: 'FAILED: ' + e.message.slice(0,100), errors: [] });
   }
-  const afterHamburger = await page.screenshot({ clip: { x: 0, y: 0, width: 500, height: 400 } });
-  append({
-    run: runIdx, ts: new Date().toISOString(), selector: '#chromeBtn', label: 'hamburger ☰ Controls',
-    action: 'click', ok: hamburgerOk,
-    effect: `panel_revealed=${hamburgerOk}`,
-    errors: [...consoleErrors.splice(0), ...pageErrors.splice(0)]
-  });
+  append({ run: runIdx, ts: new Date().toISOString(), selector: '#chromeBtn', label: 'hamburger ☰ Controls',
+    action: 'click', ok: hamburgerOk, effect: `panel_revealed=${hamburgerOk}`,
+    errors: [...consoleErrors.splice(0)] });
+
+  await page.screenshot({ path: path.join(recDir, '01-after-hamburger.png') });
 
-  // ---- Discover all interactive elements (now panel is open) ----
-  const elements = await page.evaluate(() => {
-    const els = [];
+  // ---- Discover all visible elements after panel open ----
+  // IMPORTANT: We do NOT include #mrToggle in click list (it would collapse the music room section)
+  const rawEls = await page.evaluate(() => {
+    const result = [];
     const seen = new Set();
 
-    // Priority elements: hamburger, MR panel controls, year bars, show controls
-    const selectors = [
-      { sel: '#chromeBtn', label: 'hamburger toggle' },
-      { sel: '#mrToggle', label: 'Music Room collapse toggle' },
-      { sel: '#mrSearch', label: 'search box' },
-      { sel: '#mrAll', label: 'All years button' },
-      { sel: '.mr-yr:nth-child(1)', label: 'year bar 1965' },
-      { sel: '.mr-yr:nth-child(6)', label: 'year bar 1970' },
-      { sel: '.mr-yr:nth-child(11)', label: 'year bar 1975' },
-      { sel: '.mr-yr:nth-child(16)', label: 'year bar 1980' },
-      { sel: '.mr-yr:nth-child(21)', label: 'year bar 1985' },
-      { sel: '.mr-yr:nth-child(26)', label: 'year bar 1990' },
-      { sel: '.mr-yr:nth-child(31)', label: 'year bar 1995' },
-      { sel: '#mrShow', label: 'show select dropdown' },
-      { sel: '#mrPrev', label: 'prev show ⏮' },
-      { sel: '#mrPlay', label: 'play button ▶' },
-      { sel: '#mrNext', label: 'next show ⏭' },
-      { sel: '#mrTracksToggle', label: 'Tracks toggle' },
-      { sel: '#mrSeek', label: 'seek slider' },
-      { sel: '#playBtn', label: 'scene pause/play' },
-      { sel: '#restartBtn', label: 'restart scene' },
-      { sel: '#reduceBtn', label: 'reduced motion' },
-      { sel: '#trailBtn', label: 'motion trails' },
-      { sel: '#reactBtn', label: 'react to music' },
-      { sel: '#pteroBtn', label: 'fly over dino' },
-      { sel: '#venueBtn', label: 'venue button' },
-      { sel: '#tempo', label: 'tempo slider' },
-      { sel: '#lantern', label: 'lantern slider' },
-      { sel: '#crowd', label: 'crowd slider' },
-      { sel: '#animals', label: 'animals slider' },
-      { sel: '#bears', label: 'bears slider' },
-      { sel: '#labelBtn', label: 'museum label toggle' },
-      { sel: '#backlink', label: 'back to main stage' },
-      { sel: '[id=""][class=""][text="I · Departure"]', label: 'chapter I departure' },
-    ];
-
-    // Also enumerate all remaining buttons dynamically
-    document.querySelectorAll('button, [role=button], header[id]').forEach(el => {
-      const id = el.id ? `#${el.id}` : null;
-      const cls = el.className ? `.${el.className.trim().split(/\s+/).join('.')}` : null;
-      const key = id || cls || el.tagName;
-      if (!seen.has(key)) {
-        seen.add(key);
-        const rect = el.getBoundingClientRect();
-        els.push({
-          selector: id || (cls ? (el.tagName.toLowerCase() + cls) : el.tagName.toLowerCase()),
-          label: el.textContent?.trim().slice(0, 50) || id || 'unlabeled',
-          type: el.tagName.toLowerCase() === 'input' ? el.type : 'button',
-          rect: { x: Math.round(rect.x), y: Math.round(rect.y), w: Math.round(rect.width), h: Math.round(rect.height) },
-          visible: rect.width > 0 && rect.height > 0
-        });
-      }
+    document.querySelectorAll('button, [role=button], input, select, a[href]').forEach(el => {
+      const id = el.id;
+      const cls = el.className.toString().trim();
+      const key = id ? `#${id}` : (cls ? `${el.tagName.toLowerCase()}.${cls.split(/\s+/).join('.')}` : null);
+      if (!key || seen.has(key)) return;
+      seen.add(key);
+
+      const rect = el.getBoundingClientRect();
+      if (rect.width === 0 && rect.height === 0) return; // skip invisible
+
+      result.push({
+        selector: id ? `#${id}` : `${el.tagName.toLowerCase()}.${cls.split(/\s+/)[0]}`,
+        label: el.textContent?.trim().slice(0, 50) || el.id || el.placeholder || 'unlabeled',
+        type: el.tagName === 'INPUT' ? el.type : (el.tagName === 'SELECT' ? 'select' : 'button'),
+        rect: { x: Math.round(rect.x), y: Math.round(rect.y), w: Math.round(rect.width), h: Math.round(rect.height) }
+      });
     });
 
-    // Inputs
-    document.querySelectorAll('input, select').forEach(el => {
-      const id = el.id ? `#${el.id}` : null;
-      if (id && !seen.has(id)) {
-        seen.add(id);
-        const rect = el.getBoundingClientRect();
-        els.push({
-          selector: id,
-          label: el.id || 'input',
-          type: el.type || 'select',
-          rect: { x: Math.round(rect.x), y: Math.round(rect.y), w: Math.round(rect.width), h: Math.round(rect.height) },
-          visible: rect.width > 0 && rect.height > 0
-        });
-      }
+    return result;
+  });
+
+  // Filter out navigation links (would leave page), and mrToggle (would collapse music room section)
+  const els = rawEls.filter(e =>
+    e.selector !== '#backlink' &&
+    e.selector !== '#mrToggle' &&
+    e.selector !== '#chromeBtn'  // already clicked
+  );
+
+  console.log(`  Run ${runIdx}: ${els.length} elements to interact with`);
+
+  // ---- Year bar measurements BEFORE clicking ----
+  const yearBarMeasure = await page.evaluate(() => {
+    const bars = Array.from(document.querySelectorAll('.mr-yr'));
+    const containerEl = bars[0]?.parentElement;
+    if (!containerEl) return { found: false };
+    const cRect = containerEl.getBoundingClientRect();
+    const barRects = bars.map(b => {
+      const r = b.getBoundingClientRect();
+      return { text: b.textContent?.trim(), x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height) };
     });
+    return {
+      found: true,
+      containerW: Math.round(cRect.width),
+      containerScrollW: containerEl.scrollWidth,
+      overflows: containerEl.scrollWidth > cRect.width + 2,
+      barCount: bars.length,
+      firstBar: barRects[0],
+      lastBar: barRects[barRects.length - 1],
+      allBarsW11: barRects.every(b => b.w === 11),
+      narrowBars: barRects.filter(b => b.w < 20).length,
+      sampleRects: barRects.slice(0, 3)
+    };
+  });
+
+  append({ run: runIdx, ts: new Date().toISOString(), selector: 'YEAR_MAP_MEASURE',
+    label: 'year bar measurements', action: 'measure', ok: true,
+    effect: JSON.stringify(yearBarMeasure), errors: [] });
+
+  console.log(`  Year bars: count=${yearBarMeasure.barCount} overflow=${yearBarMeasure.overflows} narrow=${yearBarMeasure.narrowBars} first=${JSON.stringify(yearBarMeasure.firstBar)}`);
 
-    return els;
+  // ---- "All years" button size ----
+  const allBtnMeasure = await page.evaluate(() => {
+    const btn = document.querySelector('#mrAll');
+    if (!btn) return { found: false };
+    const r = btn.getBoundingClientRect();
+    return { found: true, w: Math.round(r.width), h: Math.round(r.height), x: Math.round(r.x), y: Math.round(r.y), text: btn.textContent?.trim() };
   });
 
-  const visibleEls = elements.filter(e => e.visible);
-  console.log(`  Run ${runIdx}: ${visibleEls.length} visible elements, ${elements.length} total`);
+  append({ run: runIdx, ts: new Date().toISOString(), selector: '#mrAll:measure',
+    label: '"All years" button size', action: 'measure', ok: true,
+    effect: JSON.stringify(allBtnMeasure), errors: [] });
 
-  // Apply run-specific ordering
-  const ordered = orderFor(runIdx, visibleEls);
+  // ---- Seek slider height ----
+  const seekMeasure = await page.evaluate(() => {
+    const el = document.querySelector('#mrSeek');
+    if (!el) return { found: false };
+    const r = el.getBoundingClientRect();
+    return { found: true, w: Math.round(r.width), h: Math.round(r.height) };
+  });
 
-  // ---- Iterate and click/interact every element ----
-  for (const el of ordered) {
-    const errsBefore = [...consoleErrors];
-    const pageErrsBefore = [...pageErrors];
+  append({ run: runIdx, ts: new Date().toISOString(), selector: '#mrSeek:measure',
+    label: 'seek slider dimensions', action: 'measure', ok: true,
+    effect: JSON.stringify(seekMeasure), errors: [] });
 
-    // Capture before-state for key elements
-    let beforeState = '';
-    try {
-      if (el.selector === '#mrAll') {
-        beforeState = await page.evaluate(() => {
-          const btn = document.querySelector('#mrAll');
-          return btn ? `classList=${btn.className} text=${btn.textContent?.trim()}` : 'not found';
-        });
-      } else if (el.selector && el.selector.includes('mr-yr')) {
-        beforeState = await page.evaluate(sel => {
-          const btn = document.querySelector(sel);
-          return btn ? `classList=${btn.className} aria=${btn.getAttribute('aria-pressed')||'none'}` : 'not found';
-        }, el.selector);
-      }
-    } catch {}
+  // ---- Header / label visibility check ----
+  const headerCheck = await page.evaluate(() => {
+    const header = document.querySelector('.mr-header, [class*=header]');
+    const label = document.querySelector('[aria-label*="Touring"], [aria-label*="year"], h2, h3');
+    const allEl = document.querySelector('#mrAll');
+    const searchEl = document.querySelector('#mrSearch');
+    return {
+      hasHeader: !!header,
+      headerText: header?.textContent?.trim().slice(0, 80),
+      hasLabel: !!label,
+      labelText: label?.textContent?.trim().slice(0, 80),
+      allBtnFound: !!allEl,
+      searchFound: !!searchEl,
+      bodyHTML_sample: document.querySelector('.musicroom')?.innerHTML?.slice(0, 600)
+    };
+  });
 
+  append({ run: runIdx, ts: new Date().toISOString(), selector: 'HEADER_CHECK',
+    label: 'header/label visibility', action: 'measure', ok: true,
+    effect: JSON.stringify(headerCheck), errors: [] });
+
+  // ---- Apply ordering for this run ----
+  const ordered = orderFor(runIdx, els);
+
+  // ---- Click/interact every element ----
+  for (const el of ordered) {
+    const errsBefore = consoleErrors.length;
     let ok = false;
     let effect = '';
     let action = 'click';
     let errMsg = '';
 
+    // Snapshot before for key elements
+    let beforeState = '';
+    if (el.selector === '#mrAll') {
+      beforeState = await page.evaluate(() => {
+        const b = document.querySelector('#mrAll');
+        return b ? `cls=${b.className} text=${b.textContent?.trim()}` : '';
+      });
+    }
+
     try {
       if (el.type === 'range') {
-        // Sliders: drag to middle, then to each extreme
         action = 'drag';
-        const sliderEl = await page.$(el.selector);
-        if (sliderEl) {
-          await sliderEl.scrollIntoViewIfNeeded();
-          const box = await sliderEl.boundingBox();
-          if (box && box.width > 0) {
-            // drag to 50%
-            await page.mouse.move(box.x + box.width * 0.5, box.y + box.height * 0.5);
-            await page.mouse.click(box.x + box.width * 0.5, box.y + box.height * 0.5);
-            await page.waitForTimeout(200);
-            // drag to max
-            await page.mouse.move(box.x + box.width * 0.9, box.y + box.height * 0.5);
-            await page.mouse.click(box.x + box.width * 0.9, box.y + box.height * 0.5);
-            await page.waitForTimeout(200);
-            ok = true;
-            effect = `slider dragged box=${JSON.stringify(box)}`;
-          } else {
-            effect = `slider not visible/zero-width box=${JSON.stringify(box)}`;
-          }
-        }
-      } else if (el.selector === '#mrSearch') {
+        // Click at center, then at 80% position using coordinates
+        const cx = el.rect.x + el.rect.w * 0.5;
+        const cy = el.rect.y + el.rect.h * 0.5;
+        const r8 = el.rect.x + el.rect.w * 0.8;
+        await page.mouse.click(cx, cy);
+        await page.waitForTimeout(100);
+        await page.mouse.click(r8, cy);
+        await page.waitForTimeout(100);
+        const val = await page.evaluate(sel => { const e = document.querySelector(sel); return e?.value; }, el.selector);
+        effect = `dragged box=${el.rect.w}x${el.rect.h} val=${val}`;
+        ok = true;
+      } else if (el.type === 'search' || el.selector === '#mrSearch') {
         action = 'type';
-        const input = await page.$('#mrSearch');
-        if (input) {
-          await input.scrollIntoViewIfNeeded();
-          await input.click();
-          await input.fill('1972');
-          await page.waitForTimeout(500);
-          const results = await page.evaluate(() => {
-            const list = document.querySelectorAll('#mrList option, .mr-list option, .mr-result');
-            return list.length;
-          });
-          effect = `typed 1972, results visible: ${results}`;
-          await input.fill(''); // clear
-          ok = true;
-        }
-      } else if (el.selector === '#mrShow') {
+        await page.mouse.click(el.rect.x + el.rect.w * 0.5, el.rect.y + el.rect.h * 0.5);
+        await page.waitForTimeout(200);
+        await page.keyboard.type('1972');
+        await page.waitForTimeout(600);
+        const srchResult = await page.evaluate(() => {
+          const sel = document.querySelector('#mrShow');
+          return sel ? `${Array.from(sel.options).length} options visible` : 'select not found';
+        });
+        effect = `typed '1972', results: ${srchResult}`;
+        // Clear
+        await page.keyboard.selectAll();
+        await page.keyboard.press('Backspace');
+        await page.waitForTimeout(300);
+        ok = true;
+      } else if (el.type === 'select') {
         action = 'select';
-        const select = await page.$('#mrShow');
-        if (select) {
-          await select.scrollIntoViewIfNeeded();
-          const opts = await page.evaluate(() => {
-            const s = document.querySelector('#mrShow');
-            return s ? Array.from(s.options).length : 0;
-          });
-          effect = `select has ${opts} options`;
-          // Pick option at index 2 if it exists
-          if (opts > 2) {
-            await page.selectOption('#mrShow', { index: 2 });
-            await page.waitForTimeout(500);
-          }
-          ok = true;
+        const optCount = await page.evaluate(sel => {
+          const s = document.querySelector(sel);
+          return s ? Array.from(s.options).length : 0;
+        }, el.selector);
+        effect = `${optCount} options`;
+        if (optCount > 1) {
+          // Pick index 1 (second item)
+          await page.selectOption(el.selector, { index: 1 });
+          await page.waitForTimeout(500);
         }
-      } else if (['#backlink'].includes(el.selector)) {
-        // Don't navigate away — just note it
-        action = 'skip-navigate';
         ok = true;
-        effect = 'navigation link — skipped to avoid leaving page';
       } else {
-        // Standard click
-        const elHandle = await page.$(el.selector);
-        if (elHandle) {
-          await elHandle.scrollIntoViewIfNeeded();
-          const box = await elHandle.boundingBox();
-          if (box) {
-            effect = `box=${Math.round(box.width)}x${Math.round(box.height)} at (${Math.round(box.x)},${Math.round(box.y)})`;
-          }
-          await elHandle.click({ timeout: 3000 });
-          await page.waitForTimeout(400);
-          ok = true;
+        // Click by coordinates (bypasses scrollIntoView stability checks)
+        const cx = el.rect.x + el.rect.w * 0.5;
+        const cy = el.rect.y + el.rect.h * 0.5;
+        if (cx < 0 || cy < 0 || cx > 1600 || cy > 900) {
+          effect = `out of viewport at (${cx},${cy}) -- skipped`;
+          ok = false;
         } else {
-          effect = 'element not found in DOM at click time';
+          await page.mouse.click(cx, cy);
+          await page.waitForTimeout(350);
+          effect = `clicked at (${Math.round(cx)},${Math.round(cy)}) box=${el.rect.w}x${el.rect.h}`;
+          ok = true;
         }
       }
-    } catch (e) {
+    } catch(e) {
       errMsg = e.message.slice(0, 200);
       effect = `ERROR: ${errMsg}`;
     }
 
     // Capture after-state
     let afterState = '';
-    try {
-      if (el.selector === '#mrAll') {
-        afterState = await page.evaluate(() => {
-          const btn = document.querySelector('#mrAll');
-          const yr = document.querySelector('#mrYear');
-          return btn ? `classList=${btn.className} yearInput=${yr?.value}` : 'not found';
-        });
-      } else if (el.type === 'range') {
-        afterState = await page.evaluate(sel => {
-          const inp = document.querySelector(sel);
-          return inp ? `value=${inp.value}` : 'not found';
-        }, el.selector);
-      } else if (el.selector === '#mrPlay' || el.selector === '#playBtn') {
-        afterState = await page.evaluate(() => {
-          const mp = document.querySelector('#mrPlay');
-          return mp ? `mrPlay text=${mp.textContent?.trim()}` : '';
-        });
-      }
-    } catch {}
+    if (el.selector === '#mrAll') {
+      afterState = await page.evaluate(() => {
+        const b = document.querySelector('#mrAll');
+        const yi = document.querySelector('#mrYear');
+        return b ? `cls=${b.className} text=${b.textContent?.trim()} yearVal=${yi?.value}` : '';
+      });
+    } else if (el.selector && el.selector.match(/mrPlay|playBtn/)) {
+      afterState = await page.evaluate(() => {
+        const mp = document.querySelector('#mrPlay');
+        return mp ? `mrPlay=${mp.textContent?.trim()}` : '';
+      });
+    }
 
-    const newErrs = consoleErrors.slice(errsBefore.length);
-    const newPageErrs = pageErrors.slice(pageErrsBefore.length);
-    const allNewErrs = [...newErrs, ...newPageErrs];
-    if (errMsg) allNewErrs.push(errMsg);
+    const newErrs = consoleErrors.slice(errsBefore);
+    const newPageErrs = [...pageErrors.splice(0)];
+    const allNewErrs = [...newErrs, ...newPageErrs, ...(errMsg ? [errMsg] : [])];
 
     const logEntry = {
       run: runIdx,
@@ -379,176 +346,220 @@ async function runPass(runIdx) {
       label: el.label,
       action,
       ok,
-      effect: `${effect}${afterState ? ` | after:${afterState}` : ''}${beforeState ? ` | before:${beforeState}` : ''}`,
+      effect: [effect, afterState ? `after:${afterState}` : '', beforeState ? `before:${beforeState}` : ''].filter(Boolean).join(' | '),
       errors: allNewErrs
     };
 
     if (allNewErrs.length) {
-      console.log(`  [RUN${runIdx}] ERROR ${el.label}: ${allNewErrs[0]}`);
+      console.log(`  [R${runIdx}] ERR  ${el.label.slice(0,30)}: ${allNewErrs[0].slice(0,80)}`);
     } else {
-      console.log(`  [RUN${runIdx}] OK    ${el.label}: ${effect.slice(0, 80)}`);
+      console.log(`  [R${runIdx}] OK   ${el.label.slice(0,30)}: ${effect.slice(0, 60)}`);
     }
 
     append(logEntry);
 
-    // After clicking mrAll or a year bar, capture quick state snapshot
-    if (el.selector === '#mrAll' || (el.selector && el.selector.includes('mr-yr'))) {
-      try {
-        const yrState = await page.evaluate(() => {
-          const allBtn = document.querySelector('#mrAll');
-          const yearBars = Array.from(document.querySelectorAll('.mr-yr'));
-          const active = yearBars.filter(b => b.classList.contains('active') || b.getAttribute('aria-pressed') === 'true');
-          const resultList = document.querySelector('#mrList, .mr-results, [id*=List]');
-          return {
-            allBtnClasses: allBtn?.className,
-            activeBarCount: active.length,
-            activeBarTexts: active.map(b => b.textContent?.trim()).slice(0, 3),
-            resultListCount: resultList ? resultList.querySelectorAll('option, li, .mr-result').length : 'N/A'
-          };
-        });
-        append({
-          run: runIdx, ts: new Date().toISOString(),
-          selector: el.selector + ':state-snapshot', label: 'year map state after click',
-          action: 'snapshot', ok: true,
-          effect: JSON.stringify(yrState), errors: []
-        });
-      } catch {}
+    // After clicking a year bar, record state
+    if (el.selector && (el.selector.includes('mr-yr') || el.label.match(/196[0-9]|197[0-9]|198[0-9]|199[0-9]/))) {
+      const yrState = await page.evaluate(() => {
+        const allBtn = document.querySelector('#mrAll');
+        const bars = Array.from(document.querySelectorAll('.mr-yr'));
+        const active = bars.filter(b => b.classList.contains('active') || b.getAttribute('aria-pressed') === 'true');
+        return {
+          allBtnCls: allBtn?.className,
+          activeCount: active.length,
+          activeTexts: active.map(b => b.textContent?.trim())
+        };
+      });
+      append({ run: runIdx, ts: new Date().toISOString(),
+        selector: el.selector + ':yrstate', label: 'year state snapshot',
+        action: 'snapshot', ok: true, effect: JSON.stringify(yrState), errors: [] });
     }
   }
 
-  // ---- Post-pass: check for bar height timing ----
-  await page.waitForTimeout(5000); // wait extra for archive.org responses
-  const archiveStatus = await page.evaluate(() => {
+  // ---- Screenshot mid-run ----
+  await page.screenshot({ path: path.join(recDir, '02-mid-run.png') });
+
+  // ---- Wait for archive.org bar height animation ----
+  console.log(`  Waiting 6s for archive.org bar heights to load...`);
+  const waitStart = Date.now();
+  await page.waitForTimeout(6000);
+
+  const barsAfterWait = await page.evaluate(() => {
     const bars = Array.from(document.querySelectorAll('.mr-yr'));
-    const heights = bars.map(b => b.style.height || window.getComputedStyle(b).height);
-    const nonZero = heights.filter(h => h && h !== '0px' && h !== 'none');
-    const barSpans = Array.from(document.querySelectorAll('.mr-yr .bar, .bar'));
-    const spanHeights = barSpans.map(s => window.getComputedStyle(s).height);
+    const barSpans = Array.from(document.querySelectorAll('.mr-yr .bar, .mr-yr > span'));
     return {
       barCount: bars.length,
-      nonZeroHeightCount: nonZero.length,
-      sampleHeights: heights.slice(0, 5),
-      barSpanCount: barSpans.length,
-      sampleSpanHeights: spanHeights.slice(0, 5)
+      sampleParentStyles: bars.slice(0, 5).map(b => ({ style: b.style.height, computed: window.getComputedStyle(b).height })),
+      spanCount: barSpans.length,
+      sampleSpanHeights: barSpans.slice(0, 5).map(s => window.getComputedStyle(s).height),
+      hasRealHeights: bars.some(b => b.style.height && b.style.height !== '0px' && b.style.height !== 'auto'),
+      archiveDataAttr: bars.slice(0,3).map(b => ({
+        dataCount: b.getAttribute('data-count'),
+        dataYear: b.textContent?.trim()
+      }))
     };
   });
 
-  append({
-    run: runIdx, ts: new Date().toISOString(),
-    selector: 'YEAR_BARS', label: 'bar height timing check (5s after load)',
-    action: 'measure', ok: true,
-    effect: JSON.stringify(archiveStatus),
-    errors: [...archiveErrors.map(r => `archive.org FAIL: ${r.url} ${r.err}`)]
-  });
+  append({ run: runIdx, ts: new Date().toISOString(), selector: 'BAR_HEIGHT_TIMING',
+    label: `bar heights after ${Math.round((Date.now() - waitStart + 6000) / 1000)}s wait`,
+    action: 'measure', ok: true, effect: JSON.stringify(barsAfterWait),
+    errors: archiveErrors.map(e => `archive FAIL: ${e.url} ${e.err}`) });
 
-  // ---- Check for horizontal overflow (all 31 bars visible?) ----
-  const overflowCheck = await page.evaluate(() => {
-    const container = document.querySelector('.mr-map, .year-map, [class*=map], [id*=map], .mr-yr')?.parentElement;
-    if (!container) return { found: false };
-    const cRect = container.getBoundingClientRect();
-    const bars = Array.from(container.querySelectorAll('.mr-yr'));
-    const lastBar = bars[bars.length - 1];
-    const lastRect = lastBar ? lastBar.getBoundingClientRect() : null;
-    return {
-      found: true,
-      containerW: Math.round(cRect.width),
-      containerScrollW: container.scrollWidth,
-      containerOverflows: container.scrollWidth > cRect.width,
-      barCount: bars.length,
-      firstBarX: bars[0] ? Math.round(bars[0].getBoundingClientRect().x) : null,
-      lastBarX: lastRect ? Math.round(lastRect.x + lastRect.width) : null,
-      lastBarVisible: lastRect ? lastRect.right <= cRect.right + 2 : null
-    };
-  });
+  console.log(`  Archive reqs: ${archiveRequests.length} ok, ${archiveErrors.length} fail`);
+  console.log(`  Bars have real heights: ${barsAfterWait.hasRealHeights}`);
 
-  append({
-    run: runIdx, ts: new Date().toISOString(),
-    selector: 'YEAR_MAP_CONTAINER', label: 'horizontal overflow check',
-    action: 'measure', ok: true,
-    effect: JSON.stringify(overflowCheck),
-    errors: []
+  // ---- Click a specific year bar to test selection feedback ----
+  const bar1980 = await page.evaluate(() => {
+    const bars = Array.from(document.querySelectorAll('.mr-yr'));
+    const b = bars.find(b => b.textContent?.trim() === '1980') || bars[15];
+    if (!b) return null;
+    const r = b.getBoundingClientRect();
+    return { x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height) };
   });
 
-  // ---- Check "All years" reset after year selection ----
-  const resetCheck = await page.evaluate(() => {
-    const allBtn = document.querySelector('#mrAll');
-    const yearInput = document.querySelector('#mrYear');
-    return {
-      allBtnClasses: allBtn?.className,
-      yearInputVal: yearInput?.value,
-      allBtnText: allBtn?.textContent?.trim()
-    };
-  });
+  if (bar1980) {
+    const cx = bar1980.x + bar1980.w * 0.5;
+    const cy = bar1980.y + bar1980.h * 0.5;
+    console.log(`  Clicking 1980 bar at (${cx},${cy}) size ${bar1980.w}x${bar1980.h}`);
+
+    // Take screenshot before click
+    await page.screenshot({ path: path.join(recDir, '03-before-year-click.png') });
+
+    await page.mouse.click(cx, cy);
+    await page.waitForTimeout(1000);
+
+    // Check selection feedback
+    const selectionFeedback = await page.evaluate(() => {
+      const bars = Array.from(document.querySelectorAll('.mr-yr'));
+      const active = bars.filter(b => b.classList.contains('active') || b.getAttribute('aria-pressed') === 'true' || b.style.opacity === '1' || b.style.background);
+      const resultList = document.querySelector('#mrList, [id*=List], [class*=result]');
+      const header = document.querySelector('.mr-header, h3, .section-label');
+      return {
+        activeBarCount: active.length,
+        activeBarTexts: active.map(b => b.textContent?.trim()),
+        resultItems: resultList ? resultList.querySelectorAll('option, li, .item').length : 0,
+        hasVisualFeedback: active.length > 0,
+        headerText: header?.textContent?.trim()
+      };
+    });
 
-  append({
-    run: runIdx, ts: new Date().toISOString(),
-    selector: 'RESET_STATE', label: 'all-years reset state check end of run',
-    action: 'measure', ok: true,
-    effect: JSON.stringify(resetCheck),
-    errors: []
-  });
+    append({ run: runIdx, ts: new Date().toISOString(), selector: '.mr-yr[1980]',
+      label: 'click 1980 bar — selection feedback', action: 'click', ok: true,
+      effect: JSON.stringify(selectionFeedback), errors: [...consoleErrors.splice(0)] });
+
+    console.log(`  Selection feedback: ${JSON.stringify(selectionFeedback)}`);
 
-  // ---- Final screenshot for visual record ----
-  const screenshotPath = path.join(recDir, 'final-state.png');
-  await page.screenshot({ path: screenshotPath, fullPage: false });
+    await page.screenshot({ path: path.join(recDir, '04-after-year-click.png') });
+
+    // Click "All years" to reset
+    const allRect = await page.evaluate(() => {
+      const b = document.querySelector('#mrAll');
+      if (!b) return null;
+      const r = b.getBoundingClientRect();
+      return { x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height) };
+    });
+
+    if (allRect) {
+      await page.mouse.click(allRect.x + allRect.w * 0.5, allRect.y + allRect.h * 0.5);
+      await page.waitForTimeout(500);
+      const afterReset = await page.evaluate(() => {
+        const b = document.querySelector('#mrAll');
+        const yi = document.querySelector('#mrYear');
+        return { allBtnCls: b?.className, yearVal: yi?.value };
+      });
+      append({ run: runIdx, ts: new Date().toISOString(), selector: '#mrAll:reset',
+        label: 'All years reset after 1980 click', action: 'click', ok: true,
+        effect: JSON.stringify(afterReset), errors: [] });
+    }
+  }
+
+  // ---- State leak check: reload and measure ----
+  // (done only on run 2 to check if selected year persists across reload)
+  if (runIdx === 2) {
+    const stateBeforeReload = await page.evaluate(() => {
+      return {
+        mrYearVal: document.querySelector('#mrYear')?.value,
+        allBtnCls: document.querySelector('#mrAll')?.className,
+        localStorageKeys: Object.keys(localStorage)
+      };
+    });
+
+    await page.reload({ waitUntil: 'domcontentloaded', timeout: 20000 });
+    await page.waitForTimeout(2000);
+    // Reopen panel
+    await page.click('#chromeBtn', { force: true, timeout: 5000 }).catch(() => {});
+    await page.waitForTimeout(500);
+
+    const stateAfterReload = await page.evaluate(() => {
+      return {
+        mrYearVal: document.querySelector('#mrYear')?.value,
+        allBtnCls: document.querySelector('#mrAll')?.className,
+        localStorageKeys: Object.keys(localStorage)
+      };
+    });
+
+    append({ run: runIdx, ts: new Date().toISOString(), selector: 'STATE_LEAK_CHECK',
+      label: 'state leak: reload test', action: 'reload', ok: true,
+      effect: `before=${JSON.stringify(stateBeforeReload)} after=${JSON.stringify(stateAfterReload)}`,
+      errors: [] });
+    console.log(`  State leak check: before=${JSON.stringify(stateBeforeReload)} after=${JSON.stringify(stateAfterReload)}`);
+  }
+
+  // ---- Final screenshot ----
+  await page.screenshot({ path: path.join(recDir, '05-final.png') });
 
-  // Close and let video finalize
   await ctx.close();
   await browser.close();
 
-  // Convert webm to mp4 if ffmpeg available
+  // Convert webm -> mp4
   const recFiles = fs.readdirSync(recDir).filter(f => f.endsWith('.webm'));
+  const mp4Files = [];
   for (const webm of recFiles) {
     const webmPath = path.join(recDir, webm);
     const mp4Path  = webmPath.replace('.webm', '.mp4');
     try {
-      const { execSync } = require('child_process');
-      execSync(`ffmpeg -y -i "${webmPath}" -c:v libx264 -preset fast -crf 28 "${mp4Path}" 2>/dev/null`, { timeout: 60000 });
-      console.log(`  Converted: ${mp4Path}`);
-    } catch (e) {
-      console.log(`  ffmpeg convert failed for ${webm}: ${e.message.slice(0, 100)}`);
+      execSync(`ffmpeg -y -i "${webmPath}" -c:v libx264 -preset fast -crf 28 "${mp4Path}" 2>/dev/null`, { timeout: 90000 });
+      mp4Files.push(mp4Path);
+      console.log(`  Converted: ${path.basename(mp4Path)}`);
+    } catch(e) {
+      console.log(`  ffmpeg failed: ${e.message.slice(0,80)}`);
+      mp4Files.push(webmPath); // keep webm reference
     }
   }
 
-  // Summary
-  const runEntries = prior.filter(e => e.run === runIdx);
-  const runErrors  = runEntries.filter(e => e.errors && e.errors.length > 0);
-  console.log(`  Run ${runIdx} complete. Archive requests: ${archiveRequests.length}, errors: ${archiveErrors.length}`);
-  console.log(`  Bar height status: ${JSON.stringify(archiveStatus)}`);
-
   return {
-    runIdx,
+    runIdx, recDir,
+    recordings: mp4Files,
     archiveRequests: archiveRequests.length,
     archiveErrors: archiveErrors.length,
-    archiveStatus,
-    overflowCheck,
-    resetCheck,
-    screenshotPath,
-    recDir,
-    recFiles: fs.readdirSync(recDir)
+    archiveStatus: barsAfterWait,
+    yearBarMeasure,
+    allBtnMeasure,
+    seekMeasure,
+    headerCheck
   };
 }
 
-// Main
 (async () => {
   const results = [];
   for (let r = 0; r < 5; r++) {
     const res = await runPass(r);
     results.push(res);
-    // Brief pause between runs
-    if (r < 4) await new Promise(resolve => setTimeout(resolve, 2000));
+    if (r < 4) await new Promise(resolve => setTimeout(resolve, 1500));
   }
 
-  console.log('\n===== ALL RUNS COMPLETE =====');
+  console.log('\n===== ALL 5 RUNS COMPLETE =====');
   for (const res of results) {
-    console.log(`Run ${res.runIdx}: archive=${res.archiveRequests} reqs, ${res.archiveErrors} errs, overflow=${JSON.stringify(res.overflowCheck)}`);
+    console.log(`Run ${res.runIdx}: archive=${res.archiveRequests} reqs/${res.archiveErrors} errs recs=${res.recordings.map(p => path.basename(p))}`);
   }
 
-  // Output results as JSON for report generation
-  fs.writeFileSync('/Users/macstudio3/Projects/dead-agentabrams/screenrecord/run-results.json', JSON.stringify(results, null, 2));
-  console.log('\nRun results saved to screenrecord/run-results.json');
-  console.log('Log saved to ' + LOG);
+  fs.writeFileSync(
+    '/Users/macstudio3/Projects/dead-agentabrams/screenrecord/run-results.json',
+    JSON.stringify(results, null, 2)
+  );
+  console.log('\nLog: ' + LOG);
+  console.log('Results: /Users/macstudio3/Projects/dead-agentabrams/screenrecord/run-results.json');
 })().catch(e => {
-  console.error('FATAL:', e.message);
+  console.error('FATAL:', e.message, e.stack?.slice(0, 400));
   process.exit(1);
 });

← f1c7dbc Expand curated shows to 17 (1969–1994, verified LMA soundboa  ·  back to Dead Agentabrams  ·  dock UI: lit-lantern active state (soft gold wash, not solid c1728cc →