← back to Scarlet Riverboat Masquerade
Concert venue backdrops (per-show stage-light beams + venue marquee: Fillmore/Veneta/Jai-Alai/Barton Hall/Nassau, key v) + song-motif layer that floats item glyphs matching the current track title (fire/mountain, sugar/rose, cat, train, rain/snow...)
c67e4f5da8132787b987ca26724557b4cd853613 · 2026-09-01 22:24:06 -0700 · Steve Abrams
Files touched
Diff
commit c67e4f5da8132787b987ca26724557b4cd853613
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Sep 1 22:24:06 2026 -0700
Concert venue backdrops (per-show stage-light beams + venue marquee: Fillmore/Veneta/Jai-Alai/Barton Hall/Nassau, key v) + song-motif layer that floats item glyphs matching the current track title (fire/mountain, sugar/rose, cat, train, rain/snow...)
---
index.html | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 97 insertions(+)
diff --git a/index.html b/index.html
index 0df967b..b9dda3c 100644
--- a/index.html
+++ b/index.html
@@ -272,6 +272,7 @@
<button class="btn" id="trailBtn" aria-pressed="true">✧ Motion Trails</button>
<button class="btn" id="reactBtn" aria-pressed="false">♫ React to Music</button>
<button class="btn" id="pteroBtn">🦖 Fly Over</button>
+ <button class="btn" id="venueBtn" aria-pressed="true">🏛 Venue</button>
</div>
</div>
<div class="group">
@@ -411,6 +412,89 @@ function pal(key,i){ // blended palette between integer chapters
// Choreography snaps to the nearest chapter (palette blends, steps change crisply)
function CH(){ return CHAPTERS[clamp(Math.round(S.chapterMix),0,2)].choreo; }
+// ---- Concert venue backdrops (swap with the streaming show) ----
+const VENUES=[
+ {name:'Fillmore West · San Francisco · 1969', lights:['#ffcf6e','#ff7a4a','#ffd06e']},
+ {name:'Veneta · Oregon · 1972', lights:['#ffe0a0','#ffbf6a','#fff0c0']},
+ {name:'Jai-Alai Fronton · Miami · 1974', lights:['#a8c8ff','#ffd0f0','#8fe0ff']},
+ {name:'Barton Hall · Cornell · 1977', lights:['#ffd0f0','#c9a2ff','#ffe08a']},
+ {name:'Nassau Coliseum · Uniondale · 1990', lights:['#80e0ff','#ff8ad0','#ffd66e']}
+];
+let venueOn=true;
+
+// ---- Song-motif layer: floats items drawn from the current track's title ----
+const MOTIFS=[
+ [/fire|flame|mountain|burn/, ['🔥','⛰️','✨']],
+ [/sugar|magnolia|sugaree|begonia|rose|flower|daffodil|lily|garden/, ['🌺','🌸','🌼']],
+ [/rain|snow|cold|storm|hail|winter/, ['❄️','🌧️','☁️']],
+ [/sun|sunshine|sunflower|shine|summer/,['🌞','🌻','☀️']],
+ [/moon|dark star|midnight|star/, ['🌙','⭐','✨']],
+ [/water|river|ripple|ocean|wave|sea|rowing/, ['💧','🌊']],
+ [/bird|eagle|dove|sparrow|crow/, ['🐦','🕊️']],
+ [/cat|china cat|tiger|lion/, ['🐱','🐾']],
+ [/train|casey|railroad|jones|engine/, ['🚂','🛤️']],
+ [/truck|road|highway|ramble|wheel|turnin/, ['🚚','🛣️','🎡']],
+ [/devil|hell|deal|gambl|dice|card/, ['🃏','🎲']],
+ [/eyes|world/, ['👁️','🌍']],
+ [/wine|whiskey|beer|jug|drink/, ['🍷','🥃']],
+ [/love|heart|sweet|darling|honey/, ['💗','💘']],
+ [/gold|silver|diamond|jewel/, ['✨','💎']],
+ [/rat|mouse|wharf/, ['🐀']],
+ [/candy|man/, ['🍬']]
+];
+let motifParticles=[], motifClock=0, motifSet=null, motifKey=null;
+function currentMotifs(){
+ const tr=((window.SRM&&window.SRM.track)||'').toLowerCase();
+ if(tr===motifKey) return motifSet;
+ motifKey=tr; motifSet=null;
+ for(const m of MOTIFS){ if(m[0].test(tr)){ motifSet=m[1]; break; } }
+ return motifSet;
+}
+function drawSongMotifs(dt){
+ const set=currentMotifs();
+ if(set && S.playing && !S.reduced){
+ motifClock+=dt;
+ if(motifClock>0.75 && motifParticles.length<40){ motifClock=0;
+ motifParticles.push({x:S.W*(0.08+Math.random()*0.84), y:S.H*0.9, vy:-(18+Math.random()*26), drift:(Math.random()-0.5)*14, life:1, size:20+Math.random()*20, g:set[(Math.random()*set.length)|0]});
+ }
+ }
+ if(!motifParticles.length) return;
+ ctx.save(); ctx.textAlign='center'; ctx.textBaseline='middle';
+ for(let i=motifParticles.length-1;i>=0;i--){ const p=motifParticles[i];
+ p.y+=p.vy*dt; p.x+=p.drift*dt; p.life-=dt*0.16;
+ if(p.life<=0 || p.y<S.H*0.14){ motifParticles.splice(i,1); continue; }
+ ctx.globalAlpha=Math.max(0,Math.min(0.9,p.life));
+ ctx.font=`${p.size}px "Apple Color Emoji","Segoe UI Emoji",serif`;
+ ctx.fillText(p.g, p.x, p.y);
+ }
+ ctx.restore(); ctx.globalAlpha=1;
+}
+
+function drawVenue(){
+ if(!venueOn) return;
+ const W=S.W,H=S.H;
+ const idx=clamp((window.SRM&&window.SRM.showIdx!=null)?window.SRM.showIdx:3,0,VENUES.length-1);
+ const V=VENUES[idx];
+ // stage-light beams fanning down from the rig, gently sweeping, brightening on the beat
+ ctx.save(); ctx.globalCompositeOperation='lighter';
+ for(let i=0;i<4;i++){
+ const bx=W*(0.16+0.22*i), sweep=Math.sin(S.t*0.5+i*1.3)*W*0.06, tx=bx+sweep;
+ ctx.globalAlpha=0.045+(S.amp||0)*0.06+(S.pulse||0)*0.05;
+ ctx.fillStyle=V.lights[i%V.lights.length];
+ ctx.beginPath(); ctx.moveTo(bx-16,0); ctx.lineTo(bx+16,0); ctx.lineTo(tx+78,H*0.62); ctx.lineTo(tx-78,H*0.62); ctx.closePath(); ctx.fill();
+ }
+ ctx.restore(); ctx.globalAlpha=1;
+ // venue marquee
+ ctx.save();
+ const fs=Math.max(13,Math.min(21,W/60));
+ ctx.font=`italic 600 ${fs}px "Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif`;
+ ctx.textAlign='center'; ctx.textBaseline='middle';
+ ctx.shadowColor='rgba(0,0,0,0.65)'; ctx.shadowBlur=14;
+ ctx.globalAlpha=0.92; ctx.fillStyle=pal('accent',0);
+ ctx.fillText(V.name, W*0.5, H*0.14);
+ ctx.restore();
+}
+
// cutout ("South Park") styling: flat fill + a bold dark outline, round joins
const OUTLINE='rgba(8,3,5,0.92)';
function cut(fill,w){ ctx.fillStyle=fill; ctx.strokeStyle=OUTLINE; ctx.lineWidth=w||2.4; ctx.lineJoin='round'; ctx.lineCap='round'; }
@@ -1130,6 +1214,8 @@ function frame(now){
drawRiver();
drawAnimals();
drawBoat();
+ drawVenue();
+ drawSongMotifs(dt);
drawPtero(dt);
drawFireworks(dt);
@@ -1253,6 +1339,13 @@ function toggleChrome(){
}
chromeBtn.addEventListener('click', toggleChrome);
+// Venue backdrop toggle
+const venueBtn=document.getElementById('venueBtn');
+venueBtn.addEventListener('click',()=>{
+ venueOn=!venueOn; venueBtn.setAttribute('aria-pressed',String(venueOn));
+ announce(venueOn?'Venue backdrop on.':'Venue backdrop off.');
+});
+
// keyboard shortcuts
window.addEventListener('keydown',e=>{
const t=e.target;
@@ -1268,6 +1361,7 @@ window.addEventListener('keydown',e=>{
case 'a': { const rb=document.getElementById('reactBtn'); if(rb) rb.click(); } break;
case 'p': launchPtero(); break;
case 'c': toggleChrome(); break;
+ case 'v': venueBtn.click(); break;
case '1': document.querySelector('[data-cam="wide"]').click(); break;
case '2': document.querySelector('[data-cam="deck"]').click(); break;
case '3': document.querySelector('[data-cam="over"]').click(); break;
@@ -1331,6 +1425,7 @@ async function loadShow(i, opts){
const myToken=++loadToken; // guard against a stale fetch overwriting a newer selection
try{ audio.pause(); }catch(e){} // stop the current night before loading the next
showIdx=i; tracks=[]; trackIdx=-1;
+ if(window.SRM) window.SRM.showIdx=i; // tell the visuals which venue to render
const s=CATALOG[i];
sel.value=String(i);
$('mrNow').textContent=`${s.title} — loading set list…`;
@@ -1352,6 +1447,7 @@ async function loadShow(i, opts){
if(!tracks.length) throw new Error('no audio files');
renderTracks();
trackIdx=0; audio.src=tracks[0].url; // cue the opener, paused (playback stays user-initiated)
+ if(window.SRM) window.SRM.track=tracks[0].title;
$('mrNow').textContent=`♪ ${tracks[0].title}`;
highlight();
$('mrDur').textContent='0:00'; $('mrCur').textContent='0:00'; $('mrSeek').value='0';
@@ -1388,6 +1484,7 @@ function highlight(){ [...$('mrTracks').children].forEach((b,i)=>b.setAttribute(
function playTrack(i){
if(i<0||i>=tracks.length) return;
+ if(window.SRM) window.SRM.track=tracks[i].title; // tell the visuals which song is playing
if(i!==trackIdx || !audio.src){ trackIdx=i; audio.src=tracks[i].url; }
audio.play().catch(()=>setPlay(false));
$('mrNow').textContent=`♪ ${tracks[i].title}`;
← 91da570 Beef up the bear dance: bigger sway + always-grooving bounce
·
back to Scarlet Riverboat Masquerade
·
Replace emoji motifs with original DRAWN animated song-items 546a5e7 →