var szfySnowAccumulationEffect = {
name: "Snow Accumulation",
description: "Falling snowflakes gently accumulate at the bottom of the page, gradually forming a soft snow layer while continuous snowfall adds depth and a cozy winter feel.",
icon: "🏔",
type: "visual",
author: "Krishna Katariya",
thumbnail: "assets/effects/visual/thumbnails/snow-accumulation.webp",
license: "https://seazonify.com/license",
version: "1.0.0",
created: "2026-03-14",
updated: "2026-03-14",
category: "weather",
tags: ["snow", "accumulation", "winter", "falling", "buildup"],
css: [
".szfy-snow-accum-canvas {",
"  position: fixed;",
"  top: 0;",
"  left: 0;",
"  width: 100%;",
"  height: 100%;",
"  pointer-events: none;",
"  z-index: 9999;",
"}"
].join("\n"),
html: '<canvas class="szfy-snow-accum-canvas" id="szfy-snow-accum-canvas" aria-hidden="true"></canvas>',
js: [
"(function () {",
"  var canvas = document.getElementById('szfy-snow-accum-canvas');",
"  if (!canvas) return;",
"  var ctx = canvas.getContext('2d');",
"  var isMobile = window.innerWidth < 768;",
"  var MAX_PARTICLES = isMobile ? 40 : 100;",
"  var animationFrame = null;",
"  var lastTime = 0;",
"  var particles = [];",
"",
"  // Accumulation grid: stores snow height per column (pixels from bottom)",
"  var GRID_COLS = 0;",
"  var CELL_W = isMobile ? 6 : 4;",
"  var accumGrid = [];",
"  var MAX_ACCUM = isMobile ? 60 : 100;",
"",
"  function resizeCanvas() {",
"    var oldW = canvas.width;",
"    canvas.width = window.innerWidth;",
"    canvas.height = window.innerHeight;",
"    GRID_COLS = Math.ceil(canvas.width / CELL_W);",
"    // Preserve or reset accumulation grid on resize",
"    var newGrid = [];",
"    for (var c = 0; c < GRID_COLS; c++) {",
"      var oldIdx = Math.round(c * (oldW / canvas.width));",
"      newGrid[c] = (accumGrid[oldIdx] !== undefined) ? accumGrid[oldIdx] : 0;",
"    }",
"    accumGrid = newGrid;",
"  }",
"",
"  function randomBetween(a, b) { return a + Math.random() * (b - a); }",
"",
"  function getAccumHeight(x) {",
"    var col = Math.floor(x / CELL_W);",
"    if (col < 0) col = 0;",
"    if (col >= GRID_COLS) col = GRID_COLS - 1;",
"    return accumGrid[col] || 0;",
"  }",
"",
"  function addAccum(x, amount) {",
"    var col = Math.floor(x / CELL_W);",
"    if (col < 0 || col >= GRID_COLS) return;",
"    accumGrid[col] = Math.min((accumGrid[col] || 0) + amount, MAX_ACCUM);",
"    // Smooth neighbours slightly for natural slope",
"    if (col > 0) accumGrid[col - 1] = Math.min((accumGrid[col - 1] || 0) + amount * 0.25, MAX_ACCUM);",
"    if (col < GRID_COLS - 1) accumGrid[col + 1] = Math.min((accumGrid[col + 1] || 0) + amount * 0.25, MAX_ACCUM);",
"  }",
"",
"  function createParticle() {",
"    var size = randomBetween(isMobile ? 2 : 2, isMobile ? 5 : 7);",
"    return {",
"      x: randomBetween(0, canvas.width),",
"      y: randomBetween(-40, -size),",
"      size: size,",
"      speedX: randomBetween(-0.6, 0.6),",
"      speedY: randomBetween(1.2, 3.0),",
"      opacity: randomBetween(0.6, 1.0),",
"      wobble: randomBetween(0, Math.PI * 2),",
"      wobbleSpeed: randomBetween(0.02, 0.05),",
"      wobbleAmp: randomBetween(0.4, 1.2),",
"      landed: false",
"    };",
"  }",
"",
"  resizeCanvas();",
"",
"  for (var ii = 0; ii < MAX_PARTICLES; ii++) {",
"    var pp = createParticle();",
"    pp.y = randomBetween(0, canvas.height * 0.85);",
"    particles.push(pp);",
"  }",
"",
"  function update(dt) {",
"    var H = canvas.height;",
"    for (var i = 0; i < particles.length; i++) {",
"      var p = particles[i];",
"      if (p.landed) continue;",
"      p.wobble += p.wobbleSpeed;",
"      p.x += p.speedX + Math.sin(p.wobble) * p.wobbleAmp * dt * 0.04;",
"      p.y += p.speedY * dt * 0.06;",
"      // Wrap horizontally",
"      if (p.x < -p.size) p.x = canvas.width + p.size;",
"      if (p.x > canvas.width + p.size) p.x = -p.size;",
"      // Check landing: hits accumulated snow or floor",
"      var accumH = getAccumHeight(p.x);",
"      var landY = H - accumH - p.size * 0.4;",
"      if (p.y >= landY) {",
"        p.y = landY;",
"        p.landed = true;",
"        addAccum(p.x, p.size * 0.55);",
"        // Replace with fresh flake after short delay via flag",
"        (function(idx) {",
"          setTimeout(function() { particles[idx] = createParticle(); }, randomBetween(200, 800));",
"        })(i);",
"      }",
"    }",
"  }",
"",
"  function drawAccumulation() {",
"    var H = canvas.height;",
"    if (GRID_COLS < 2) return;",
"    ctx.beginPath();",
"    ctx.moveTo(0, H);",
"    for (var c = 0; c < GRID_COLS; c++) {",
"      var x = c * CELL_W;",
"      var y = H - (accumGrid[c] || 0);",
"      ctx.lineTo(x, y);",
"    }",
"    ctx.lineTo(canvas.width, H);",
"    ctx.closePath();",
"    // Layered fill: solid base + highlight",
"    var grad = ctx.createLinearGradient(0, H - MAX_ACCUM, 0, H);",
"    grad.addColorStop(0, 'rgba(220,235,255,0.95)');",
"    grad.addColorStop(0.4, 'rgba(240,248,255,0.98)');",
"    grad.addColorStop(1, 'rgba(255,255,255,1)');",
"    ctx.fillStyle = grad;",
"    ctx.fill();",
"    // Soft top edge highlight",
"    ctx.beginPath();",
"    ctx.moveTo(0, H - (accumGrid[0] || 0));",
"    for (var c2 = 1; c2 < GRID_COLS; c2++) {",
"      var cx = c2 * CELL_W;",
"      var cy = H - (accumGrid[c2] || 0);",
"      ctx.lineTo(cx, cy);",
"    }",
"    ctx.strokeStyle = 'rgba(255,255,255,0.9)';",
"    ctx.lineWidth = isMobile ? 1.5 : 2;",
"    ctx.stroke();",
"  }",
"",
"  function drawParticles() {",
"    for (var i = 0; i < particles.length; i++) {",
"      var p = particles[i];",
"      if (p.landed) continue;",
"      ctx.save();",
"      ctx.globalAlpha = p.opacity;",
"      // Soft glow for larger flakes",
"      if (p.size > 4) {",
"        var grd = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.size * 1.4);",
"        grd.addColorStop(0, 'rgba(255,255,255,0.9)');",
"        grd.addColorStop(1, 'rgba(220,235,255,0)');",
"        ctx.fillStyle = grd;",
"        ctx.beginPath();",
"        ctx.arc(p.x, p.y, p.size * 1.4, 0, Math.PI * 2);",
"        ctx.fill();",
"      }",
"      ctx.fillStyle = '#ffffff';",
"      ctx.beginPath();",
"      ctx.arc(p.x, p.y, p.size * 0.5, 0, Math.PI * 2);",
"      ctx.fill();",
"      ctx.restore();",
"    }",
"  }",
"",
"  function loop(timestamp) {",
"    var dt = Math.min(timestamp - lastTime, 50);",
"    lastTime = timestamp;",
"    ctx.clearRect(0, 0, canvas.width, canvas.height);",
"    update(dt);",
"    drawAccumulation();",
"    drawParticles();",
"    animationFrame = requestAnimationFrame(loop);",
"  }",
"",
"  function handleVisibility() {",
"    if (document.hidden) {",
"      cancelAnimationFrame(animationFrame);",
"      animationFrame = null;",
"    } else {",
"      if (!animationFrame) {",
"        lastTime = performance.now();",
"        animationFrame = requestAnimationFrame(loop);",
"      }",
"    }",
"  }",
"",
"  function resizeHandler() { resizeCanvas(); }",
"",
"  document.addEventListener('visibilitychange', handleVisibility);",
"  window.addEventListener('resize', resizeHandler);",
"  lastTime = performance.now();",
"  animationFrame = requestAnimationFrame(loop);",
"",
"  canvas._szfyDestroy = function () {",
"    cancelAnimationFrame(animationFrame);",
"    animationFrame = null;",
"    document.removeEventListener('visibilitychange', handleVisibility);",
"    window.removeEventListener('resize', resizeHandler);",
"  };",
"})();"
].join("\n")
};
if (typeof window !== "undefined" && window.SeazonifyController) {
window.SeazonifyController.injectVisualEffect(szfySnowAccumulationEffect);
}
if (typeof module !== "undefined" && module.exports) {
module.exports = { visual: szfySnowAccumulationEffect };
}