← 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 →