[object Object]

← back to Scarlet Riverboat Masquerade

Add singing songbirds: cutout shoreline birds with ♪♫♬ note-bubbles that pop rhythmically (and on the beat when reactive). Original glyphs, no lyrics — copyright-safe

b44db4f4ed0fd8e9e6affabb84e0b54b27806e5a · 2026-09-01 18:58:27 -0700 · Steve Abrams

Files touched

Diff

commit b44db4f4ed0fd8e9e6affabb84e0b54b27806e5a
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Sep 1 18:58:27 2026 -0700

    Add singing songbirds: cutout shoreline birds with ♪♫♬ note-bubbles that pop rhythmically (and on the beat when reactive). Original glyphs, no lyrics — copyright-safe
---
 index.html | 36 ++++++++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)

diff --git a/index.html b/index.html
index e0b3838..e9cfcdc 100644
--- a/index.html
+++ b/index.html
@@ -775,6 +775,7 @@ function drawAnimals(){
   rabbit(W*0.63, baseY-4, 1*scaleA, t, accent);
   bearAndFireflies(W*0.85, baseY-2, 1.1*scaleA, t, accent);
   drawBearCubs(W*0.85, baseY-2, t, scaleA);
+  drawSongbirds(t, scaleA, baseY);
 
   ctx.restore();
 }
@@ -906,6 +907,41 @@ function drawBearCubs(bx,by,t,scaleA){
   bearCub(bx+42, by, 0.9*scaleA, t, 1.7);
 }
 
+// shoreline songbirds that "sing" the music — note-bubbles pop on the beat (♪♫♬, never lyrics)
+const NOTES=['♪','♫','♬'];
+function songbird(x,y,s,t,ph){
+  const bob=Math.sin(t*3+ph)*1.4;
+  ctx.save(); ctx.translate(x,y+bob); ctx.scale(s,s);
+  cut('rgba(158,74,56,0.98)',1.8);
+  ctx.beginPath(); ctx.ellipse(0,0,6,5,0,0,7); fillOutline();               // body
+  ctx.beginPath(); ctx.arc(4,-5,3,0,7); fillOutline();                      // head
+  ctx.beginPath(); ctx.moveTo(-5,0); ctx.lineTo(-12,-3); ctx.lineTo(-6,3); ctx.closePath(); fillOutline(); // tail
+  ctx.fillStyle='rgba(242,182,74,0.95)';                                    // beak
+  ctx.beginPath(); ctx.moveTo(6,-5); ctx.lineTo(11,-4); ctx.lineTo(6,-3); ctx.closePath(); ctx.fill();
+  ctx.restore();
+  // song bubble
+  const beat=(S.pulse||0), amp=(S.amp||0), reacting=!!(window.SRM&&window.SRM.react);
+  let show,pop;
+  if(reacting){ show = amp>0.24||beat>0.15; pop = 0.7+beat*1.0+amp*0.3; }
+  else { const c=Math.sin(t*1.7+ph); show=c>-0.5; pop=0.82+Math.max(0,c)*0.4; }
+  if(show){
+    ctx.save(); ctx.translate(x+10*s, y-17*s); ctx.scale(pop*s*0.95, pop*s*0.95);
+    cut('rgba(244,231,214,0.96)',1.6);
+    ctx.beginPath(); ctx.ellipse(0,0,11,8,0,0,7); fillOutline();
+    ctx.beginPath(); ctx.moveTo(-4,6); ctx.lineTo(-9,12); ctx.lineTo(-1,7); ctx.closePath(); fillOutline(); // tail toward bird
+    ctx.fillStyle='rgba(30,10,14,0.95)';
+    ctx.font='bold 12px sans-serif'; ctx.textAlign='center'; ctx.textBaseline='middle';
+    ctx.fillText(NOTES[Math.abs((t*2+ph)|0)%3], 0, 0);
+    ctx.restore();
+  }
+}
+function drawSongbirds(t,scaleA,baseY){
+  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 ----------
 function drawRiver(){
   const W=S.W,H=S.H;

← 9486ed3 Full cutout pass: flat-fill + bold outlines on dancers (with  ·  back to Scarlet Riverboat Masquerade  ·  Museum label completeness: document A (react), P (pterodacty cda7b3e →