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