diff --git a/background-final.html b/background-final.html index 14ac221..8043074 100644 --- a/background-final.html +++ b/background-final.html @@ -1094,7 +1094,8 @@ else window.SunCalc = SunCalc; * fragment shader to produce animated cloud-like patterns. * * Drop-in replacement for Vanta when CONFIG.renderer === 'clouds2d'. - * Creates its own overlay and attaches to the target element. + * Reads day/night palette + weather theme from window globals set + * by the parent (background.html's animate() loop). */ (function() { @@ -1132,14 +1133,21 @@ var VERT_SRC = [ 'void main() { gl_Position = vec4(aPos, 0.0, 1.0); }' ].join('\n'); +// Fragment shader: animates an fBm cloud field, modulates density +// by weather, blends day/night palette colours, and adds a soft +// horizon gradient so the sky transitions naturally. var FRAG_SRC = [ 'precision highp float;', 'uniform vec2 uResolution;', 'uniform float uTime;', 'uniform vec3 uSky;', 'uniform vec3 uCloud;', + 'uniform vec3 uCloudShadow;', 'uniform vec3 uLight;', + 'uniform vec3 uBg;', 'uniform float uSpeed;', + 'uniform float uDensity;', // 0..1 how thick the clouds are + 'uniform float uDayProgress;', // 0..1 (day vs night) 'float hash(vec2 p) {', 'p = fract(p * vec2(123.34, 456.21));', 'p += dot(p, p + 45.32);', @@ -1170,10 +1178,21 @@ var FRAG_SRC = [ 'vec2 p1 = uv * vec2(3.0, 1.7) + vec2(uTime * 0.012, uTime * 0.004);', 'vec2 p2 = uv * vec2(5.0, 2.6) + vec2(uTime * 0.020, -uTime * 0.006);', 'float n = fbm(p1 * uSpeed) * 0.6 + fbm(p2 * uSpeed) * 0.4;', - 'float cloud = smoothstep(0.35, 0.75, n);', - 'float horizon = smoothstep(0.0, 0.45, uv.y);', - 'vec3 col = mix(uSky, uCloud, cloud * (1.0 - horizon * 0.3));', - 'col = mix(col, uLight, cloud * horizon * 0.5);', + // Density threshold widens with uDensity (1 = always cloudy, 0 = clear sky) + 'float thresh = mix(0.85, 0.30, uDensity);', + 'float cloud = smoothstep(thresh - 0.10, thresh + 0.10, n);', + // Soft horizon: top of frame is more sky, bottom more clouds + 'float horizon = smoothstep(0.0, 0.5, uv.y);', + // Sky gradient (top vs bottom) + 'vec3 sky = mix(uBg, uSky, horizon);', + // Cloud body (lit top + shadow bottom) + 'vec3 cloudCol = mix(uCloudShadow, uCloud, horizon);', + // Add light tint to the lit side + 'cloudCol = mix(cloudCol, uLight, cloud * horizon * 0.4 * uDayProgress);', + // Final composite: sky where there's no cloud, cloud where there is + 'vec3 col = mix(sky, cloudCol, cloud);', + // At night, blend everything toward bg color (darken) + 'col = mix(col, mix(uBg, cloudCol, 0.3), (1.0 - uDayProgress) * 0.5);', 'gl_FragColor = vec4(col, 1.0);', '}' ].join('\n'); @@ -1198,34 +1217,51 @@ function init(canvas) { var uTime = gl.getUniformLocation(prog, 'uTime'); var uSky = gl.getUniformLocation(prog, 'uSky'); var uCloud = gl.getUniformLocation(prog, 'uCloud'); + var uCloudShadow = gl.getUniformLocation(prog, 'uCloudShadow'); var uLight = gl.getUniformLocation(prog, 'uLight'); + var uBg = gl.getUniformLocation(prog, 'uBg'); var uSpeed = gl.getUniformLocation(prog, 'uSpeed'); + var uDensity = gl.getUniformLocation(prog, 'uDensity'); + var uDayProgress = gl.getUniformLocation(prog, 'uDayProgress'); return { gl: gl, prog: prog, buf: buf, aPos: aPos, uRes: uRes, uTime: uTime, uSky: uSky, uCloud: uCloud, - uLight: uLight, uSpeed: uSpeed + uCloudShadow: uCloudShadow, uLight: uLight, uBg: uBg, + uSpeed: uSpeed, uDensity: uDensity, uDayProgress: uDayProgress }; } -function hex(r, g, b) { return [r/255, g/255, b/255]; } +// Convert 24-bit int (0xRRGGBB) to [r,g,b] 0..1 floats. +// Theme-agnostic: caller decides what colour value to pass in. +function hex(rgb) { + return [((rgb >> 16) & 255) / 255, + ((rgb >> 8) & 255) / 255, + (rgb & 255) / 255]; +} + +// Cloud density per weather (0 = clear, 1 = overcast). +// Names mirror the weather themes defined in background.html. +var WEATHER_DENSITY = { + sunny: 0.20, + cloudy: 0.70, + stormy: 1.00, + night: 0.45 +}; window.clouds2dRender = function(target) { if (!target || typeof target !== 'object') { console.error('clouds2d: invalid target'); return; } - // Get or create the WebGL canvas var canvas = target.__clouds2dCanvas; if (!canvas) { - // Replace the target's content with a WebGL canvas canvas = document.createElement('canvas'); canvas.style.position = 'absolute'; canvas.style.top = '0'; canvas.style.left = '0'; canvas.style.width = '100%'; canvas.style.height = '100%'; - // Empty the target and insert the canvas as the only child while (target.firstChild) target.removeChild(target.firstChild); target.appendChild(canvas); target.style.position = 'relative'; @@ -1254,20 +1290,38 @@ window.clouds2dRender = function(target) { gl.uniform2f(s.uRes, canvas.width, canvas.height); gl.uniform1f(s.uTime, performance.now() / 1000); - var sky = [0.39, 0.54, 0.78]; - var cloud = [0.43, 0.43, 0.43]; - var light = [0.89, 0.87, 0.82]; - var cfg = window.CONFIG || {}; - var theme = (cfg.currentWeatherTheme || (cfg.weatherThemes && cfg.weatherThemes[cfg.theme])) || null; - if (theme) { - sky = hex((theme.skyColor >> 16) & 255, (theme.skyColor >> 8) & 255, theme.skyColor & 255); - cloud = hex((theme.cloudColor >> 16) & 255, (theme.cloudColor >> 8) & 255, theme.cloudColor & 255); - light = hex((theme.lightColor >> 16) & 255, (theme.lightColor >> 8) & 255, theme.lightColor & 255); + // Pull palette + weather from globals (set by animate() in background.html). + // No hardcoded fallbacks: until the parent has published a palette we + // skip the draw and leave the WebGL context cleared. This keeps the + // renderer 100% theme-agnostic — the parent fully owns colour decisions. + var pal = window.__currentPalette; + if (!pal) { + gl.clearColor(0, 0, 0, 1); + gl.clear(gl.COLOR_BUFFER_BIT); + return; } - gl.uniform3fv(s.uSky, sky); - gl.uniform3fv(s.uCloud, cloud); - gl.uniform3fv(s.uLight, light); - gl.uniform1f(s.uSpeed, 1.0); + + var weather = window.__currentWeatherName; + var dayProgress = window.__currentDayProgress; + var themeSpeed = window.__currentThemeSpeed; + + gl.uniform3fv(s.uSky, hex(pal.sky)); + gl.uniform3fv(s.uCloud, hex(pal.cloud)); + gl.uniform3fv(s.uCloudShadow, hex(pal.cloudShadow !== undefined ? pal.cloudShadow : pal.sky)); + gl.uniform3fv(s.uLight, hex(pal.light)); + gl.uniform3fv(s.uBg, hex(pal.bg)); + + var density = (weather && WEATHER_DENSITY[weather] !== undefined) + ? WEATHER_DENSITY[weather] : 0.5; + gl.uniform1f(s.uDensity, density); + + var day = (typeof dayProgress === 'number') + ? Math.max(0, Math.min(1, dayProgress)) : 0.5; + gl.uniform1f(s.uDayProgress, day); + + var speed = (typeof themeSpeed === 'number') + ? Math.max(0.1, Math.min(3, themeSpeed)) : 1.0; + gl.uniform1f(s.uSpeed, speed); gl.drawArrays(gl.TRIANGLES, 0, 3); }; @@ -2215,6 +2269,9 @@ window.weatherThemes = { return { sky: mixColor(c.sky, themeColors.skyColor, mix), cloud: mixColor(c.cloud, themeColors.cloudColor, mix), + cloudShadow: c.cloudShadow !== undefined + ? mixColor(c.cloudShadow, themeColors.cloudColor, mix * 0.8) + : mixColor(c.cloud, themeColors.cloudColor, mix * 0.8), light: mixColor(c.sun, themeColors.lightColor, mix * 0.5), bg: mixColor(c.bg, themeColors.backgroundColor, mix) }; @@ -2444,6 +2501,11 @@ window.weatherThemes = { var t = (Date.now() - startTime) / CYCLE % 1; var c = getColor(t); c = applyWeatherTheme(c); + // Expose current palette + weather info for the 2D renderer + window.__currentPalette = c; + window.__currentWeatherName = currentWeatherThemeName; + window.__currentDayProgress = realDayProgress; + window.__currentThemeSpeed = currentThemeSpeed; effect.setOptions({ skyColor: c.sky, cloudColor: c.cloud, diff --git a/background.html b/background.html index cbb9ccf..3aa990b 100644 --- a/background.html +++ b/background.html @@ -1416,6 +1416,9 @@ window.weatherThemes = { return { sky: mixColor(c.sky, themeColors.skyColor, mix), cloud: mixColor(c.cloud, themeColors.cloudColor, mix), + cloudShadow: c.cloudShadow !== undefined + ? mixColor(c.cloudShadow, themeColors.cloudColor, mix * 0.8) + : mixColor(c.cloud, themeColors.cloudColor, mix * 0.8), light: mixColor(c.sun, themeColors.lightColor, mix * 0.5), bg: mixColor(c.bg, themeColors.backgroundColor, mix) }; @@ -1645,6 +1648,11 @@ window.weatherThemes = { var t = (Date.now() - startTime) / CYCLE % 1; var c = getColor(t); c = applyWeatherTheme(c); + // Expose current palette + weather info for the 2D renderer + window.__currentPalette = c; + window.__currentWeatherName = currentWeatherThemeName; + window.__currentDayProgress = realDayProgress; + window.__currentThemeSpeed = currentThemeSpeed; effect.setOptions({ skyColor: c.sky, cloudColor: c.cloud, diff --git a/vendor/clouds2d.js b/vendor/clouds2d.js index 92094d2..7b4b923 100644 --- a/vendor/clouds2d.js +++ b/vendor/clouds2d.js @@ -4,7 +4,8 @@ * fragment shader to produce animated cloud-like patterns. * * Drop-in replacement for Vanta when CONFIG.renderer === 'clouds2d'. - * Creates its own overlay and attaches to the target element. + * Reads day/night palette + weather theme from window globals set + * by the parent (background.html's animate() loop). */ (function() { @@ -42,14 +43,21 @@ var VERT_SRC = [ 'void main() { gl_Position = vec4(aPos, 0.0, 1.0); }' ].join('\n'); +// Fragment shader: animates an fBm cloud field, modulates density +// by weather, blends day/night palette colours, and adds a soft +// horizon gradient so the sky transitions naturally. var FRAG_SRC = [ 'precision highp float;', 'uniform vec2 uResolution;', 'uniform float uTime;', 'uniform vec3 uSky;', 'uniform vec3 uCloud;', + 'uniform vec3 uCloudShadow;', 'uniform vec3 uLight;', + 'uniform vec3 uBg;', 'uniform float uSpeed;', + 'uniform float uDensity;', // 0..1 how thick the clouds are + 'uniform float uDayProgress;', // 0..1 (day vs night) 'float hash(vec2 p) {', 'p = fract(p * vec2(123.34, 456.21));', 'p += dot(p, p + 45.32);', @@ -80,10 +88,21 @@ var FRAG_SRC = [ 'vec2 p1 = uv * vec2(3.0, 1.7) + vec2(uTime * 0.012, uTime * 0.004);', 'vec2 p2 = uv * vec2(5.0, 2.6) + vec2(uTime * 0.020, -uTime * 0.006);', 'float n = fbm(p1 * uSpeed) * 0.6 + fbm(p2 * uSpeed) * 0.4;', - 'float cloud = smoothstep(0.35, 0.75, n);', - 'float horizon = smoothstep(0.0, 0.45, uv.y);', - 'vec3 col = mix(uSky, uCloud, cloud * (1.0 - horizon * 0.3));', - 'col = mix(col, uLight, cloud * horizon * 0.5);', + // Density threshold widens with uDensity (1 = always cloudy, 0 = clear sky) + 'float thresh = mix(0.85, 0.30, uDensity);', + 'float cloud = smoothstep(thresh - 0.10, thresh + 0.10, n);', + // Soft horizon: top of frame is more sky, bottom more clouds + 'float horizon = smoothstep(0.0, 0.5, uv.y);', + // Sky gradient (top vs bottom) + 'vec3 sky = mix(uBg, uSky, horizon);', + // Cloud body (lit top + shadow bottom) + 'vec3 cloudCol = mix(uCloudShadow, uCloud, horizon);', + // Add light tint to the lit side + 'cloudCol = mix(cloudCol, uLight, cloud * horizon * 0.4 * uDayProgress);', + // Final composite: sky where there's no cloud, cloud where there is + 'vec3 col = mix(sky, cloudCol, cloud);', + // At night, blend everything toward bg color (darken) + 'col = mix(col, mix(uBg, cloudCol, 0.3), (1.0 - uDayProgress) * 0.5);', 'gl_FragColor = vec4(col, 1.0);', '}' ].join('\n'); @@ -108,34 +127,51 @@ function init(canvas) { var uTime = gl.getUniformLocation(prog, 'uTime'); var uSky = gl.getUniformLocation(prog, 'uSky'); var uCloud = gl.getUniformLocation(prog, 'uCloud'); + var uCloudShadow = gl.getUniformLocation(prog, 'uCloudShadow'); var uLight = gl.getUniformLocation(prog, 'uLight'); + var uBg = gl.getUniformLocation(prog, 'uBg'); var uSpeed = gl.getUniformLocation(prog, 'uSpeed'); + var uDensity = gl.getUniformLocation(prog, 'uDensity'); + var uDayProgress = gl.getUniformLocation(prog, 'uDayProgress'); return { gl: gl, prog: prog, buf: buf, aPos: aPos, uRes: uRes, uTime: uTime, uSky: uSky, uCloud: uCloud, - uLight: uLight, uSpeed: uSpeed + uCloudShadow: uCloudShadow, uLight: uLight, uBg: uBg, + uSpeed: uSpeed, uDensity: uDensity, uDayProgress: uDayProgress }; } -function hex(r, g, b) { return [r/255, g/255, b/255]; } +// Convert 24-bit int (0xRRGGBB) to [r,g,b] 0..1 floats. +// Theme-agnostic: caller decides what colour value to pass in. +function hex(rgb) { + return [((rgb >> 16) & 255) / 255, + ((rgb >> 8) & 255) / 255, + (rgb & 255) / 255]; +} + +// Cloud density per weather (0 = clear, 1 = overcast). +// Names mirror the weather themes defined in background.html. +var WEATHER_DENSITY = { + sunny: 0.20, + cloudy: 0.70, + stormy: 1.00, + night: 0.45 +}; window.clouds2dRender = function(target) { if (!target || typeof target !== 'object') { console.error('clouds2d: invalid target'); return; } - // Get or create the WebGL canvas var canvas = target.__clouds2dCanvas; if (!canvas) { - // Replace the target's content with a WebGL canvas canvas = document.createElement('canvas'); canvas.style.position = 'absolute'; canvas.style.top = '0'; canvas.style.left = '0'; canvas.style.width = '100%'; canvas.style.height = '100%'; - // Empty the target and insert the canvas as the only child while (target.firstChild) target.removeChild(target.firstChild); target.appendChild(canvas); target.style.position = 'relative'; @@ -164,20 +200,38 @@ window.clouds2dRender = function(target) { gl.uniform2f(s.uRes, canvas.width, canvas.height); gl.uniform1f(s.uTime, performance.now() / 1000); - var sky = [0.39, 0.54, 0.78]; - var cloud = [0.43, 0.43, 0.43]; - var light = [0.89, 0.87, 0.82]; - var cfg = window.CONFIG || {}; - var theme = (cfg.currentWeatherTheme || (cfg.weatherThemes && cfg.weatherThemes[cfg.theme])) || null; - if (theme) { - sky = hex((theme.skyColor >> 16) & 255, (theme.skyColor >> 8) & 255, theme.skyColor & 255); - cloud = hex((theme.cloudColor >> 16) & 255, (theme.cloudColor >> 8) & 255, theme.cloudColor & 255); - light = hex((theme.lightColor >> 16) & 255, (theme.lightColor >> 8) & 255, theme.lightColor & 255); + // Pull palette + weather from globals (set by animate() in background.html). + // No hardcoded fallbacks: until the parent has published a palette we + // skip the draw and leave the WebGL context cleared. This keeps the + // renderer 100% theme-agnostic — the parent fully owns colour decisions. + var pal = window.__currentPalette; + if (!pal) { + gl.clearColor(0, 0, 0, 1); + gl.clear(gl.COLOR_BUFFER_BIT); + return; } - gl.uniform3fv(s.uSky, sky); - gl.uniform3fv(s.uCloud, cloud); - gl.uniform3fv(s.uLight, light); - gl.uniform1f(s.uSpeed, 1.0); + + var weather = window.__currentWeatherName; + var dayProgress = window.__currentDayProgress; + var themeSpeed = window.__currentThemeSpeed; + + gl.uniform3fv(s.uSky, hex(pal.sky)); + gl.uniform3fv(s.uCloud, hex(pal.cloud)); + gl.uniform3fv(s.uCloudShadow, hex(pal.cloudShadow !== undefined ? pal.cloudShadow : pal.sky)); + gl.uniform3fv(s.uLight, hex(pal.light)); + gl.uniform3fv(s.uBg, hex(pal.bg)); + + var density = (weather && WEATHER_DENSITY[weather] !== undefined) + ? WEATHER_DENSITY[weather] : 0.5; + gl.uniform1f(s.uDensity, density); + + var day = (typeof dayProgress === 'number') + ? Math.max(0, Math.min(1, dayProgress)) : 0.5; + gl.uniform1f(s.uDayProgress, day); + + var speed = (typeof themeSpeed === 'number') + ? Math.max(0.1, Math.min(3, themeSpeed)) : 1.0; + gl.uniform1f(s.uSpeed, speed); gl.drawArrays(gl.TRIANGLES, 0, 3); };