[object Object]

← back to Scarlet Riverboat Masquerade

Fix NaN-color crash that froze the render loop (pal() rgb strings fed to hexToRgb); add shade() helper, deeper canyon parallax, favicon

f968af593388dac0f9ef1467980269356096fc8d · 2026-09-01 16:34:21 -0700 · Steve Abrams

Files touched

Diff

commit f968af593388dac0f9ef1467980269356096fc8d
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Sep 1 16:34:21 2026 -0700

    Fix NaN-color crash that froze the render loop (pal() rgb strings fed to hexToRgb); add shade() helper, deeper canyon parallax, favicon
---
 index.html | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/index.html b/index.html
index cd04390..2d7eae1 100644
--- a/index.html
+++ b/index.html
@@ -6,6 +6,7 @@
 <meta name="description" content="Scarlet Riverboat Masquerade — an authored, animated generative gallery of a paddlewheel riverboat drifting through a crimson canyon at dusk. Steer tempo, lanterns, crowd, animals, cameras and chapters.">
 <meta name="color-scheme" content="dark">
 <title>Scarlet Riverboat Masquerade — An Animated Gallery</title>
+<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><rect width='16' height='16' fill='%237c1522'/><circle cx='8' cy='6' r='3' fill='%23f2b64a'/></svg>">
 <style>
   :root{
     --ink:#f4e7d6;
@@ -279,6 +280,16 @@ function lerp(a,b,t){return a+(b-a)*t;}
 function clamp(v,lo,hi){return Math.max(lo,Math.min(hi,v));}
 function hexToRgb(h){h=h.replace('#','');return [parseInt(h.substr(0,2),16),parseInt(h.substr(2,2),16),parseInt(h.substr(4,2),16)];}
 function mixHex(h1,h2,t){const a=hexToRgb(h1),b=hexToRgb(h2);return `rgb(${Math.round(lerp(a[0],b[0],t))},${Math.round(lerp(a[1],b[1],t))},${Math.round(lerp(a[2],b[2],t))})`;}
+// Darken any 'rgb(r,g,b)' (or hex) color toward black by t (0..1). pal() returns rgb() strings,
+// so we must NOT feed those back into hexToRgb — this handles both forms safely.
+function shade(color,t){
+  const m=String(color).match(/\d+/g);
+  let r,g,b;
+  if(String(color).charAt(0)==='#'){ const c=hexToRgb(color); r=c[0];g=c[1];b=c[2]; }
+  else if(m && m.length>=3){ r=+m[0]; g=+m[1]; b=+m[2]; }
+  else return color;
+  return `rgb(${Math.round(r*(1-t))},${Math.round(g*(1-t))},${Math.round(b*(1-t))})`;
+}
 function pal(key,i){ // blended palette between integer chapters
   const m = S.chapterMix; const lo=Math.floor(m), hi=Math.min(2,lo+1), f=m-lo;
   const A=CHAPTERS[lo][key], B=CHAPTERS[hi][key];
@@ -774,7 +785,7 @@ function drawRiver(){
   const top=H*0.72;
   const g=ctx.createLinearGradient(0,top,0,H);
   g.addColorStop(0, pal('river',0));
-  g.addColorStop(1, mixHex(pal('river',0),'#000000',0.5));
+  g.addColorStop(1, shade(pal('river',0),0.5));
   ctx.fillStyle=g; ctx.fillRect(0,top,W,H-top);
   const hi=pal('riverHi',0);
   ctx.strokeStyle=hi;
@@ -836,7 +847,7 @@ function frame(now){
   }
 
   // parallax canyon (farthest -> nearest)
-  canyonSilhouette(H*0.54, 150, 6.1, mixHex(pal('canyon',0),'#000000',0.35), Math.sin(S.t*0.03)*3);
+  canyonSilhouette(H*0.54, 150, 6.1, shade(pal('canyon',0),0.35), Math.sin(S.t*0.03)*3);
   canyonSilhouette(H*0.62, 120, 1.2, pal('canyon',0), Math.sin(S.t*0.05)*4);
   canyonSilhouette(H*0.70, 90, 4.5, pal('canyon',2), Math.sin(S.t*0.05+2)*3);
 

← a1ca1d2 Scarlet Riverboat Masquerade: working single-file animated g  ·  back to Scarlet Riverboat Masquerade  ·  Harden shade(): clamp t to [0,1] and channels to [0,255] (co 8187692 →