const canvas = document.getElementById('bg-canvas'); const ctx = canvas.getContext('2d', { alpha: false }); let width, height, dpr; let time = 0; let imageData, pixels; let noiseCanvas, noiseCtx; let noisePattern; function resizeCanvas() { width = window.innerWidth; height = window.innerHeight; dpr = Math.min(window.devicePixelRatio || 1, 1.5); canvas.width = Math.round(width * dpr); canvas.height = Math.round(height * dpr); canvas.style.width = width + 'px'; canvas.style.height = height + 'px'; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); imageData = ctx.createImageData( Math.ceil(width / 2), Math.ceil(height / 2) ); pixels = imageData.data; createNoise(); } function createNoise() { noiseCanvas = document.createElement('canvas'); noiseCanvas.width = 256; noiseCanvas.height = 256; noiseCtx = noiseCanvas.getContext('2d'); const data = noiseCtx.createImageData(256, 256); for (let i = 0; i < data.data.length; i += 4) { const v = 105 + Math.random() * 45; data.data[i] = v; data.data[i + 1] = v; data.data[i + 2] = v; data.data[i + 3] = 255; } noiseCtx.putImageData(data, 0, 0); noisePattern = ctx.createPattern(noiseCanvas, 'repeat'); } function smoothNoise(x, y, t) { return ( Math.sin(x * 1.35 + t * 0.22) * Math.cos(y * 0.75 - t * 0.16) * 0.55 + Math.sin(x * 0.65 - y * 0.8 + t * 0.12) * 0.3 + Math.cos(y * 1.6 + x * 0.3 - t * 0.1) * 0.15 ); } function velvet(x, y, t) { const nx = x / width; const ny = y / height; // Grandi deformazioni del tessuto const wave1 = smoothNoise(nx * 5, ny * 3, t); const wave2 = smoothNoise(nx * 3 - 1, ny * 5, t * 0.8); const fold = wave1 * 0.65 + wave2 * 0.35; // Pieghe morbide e direzionali const folds = Math.sin(nx * 8 + fold * 1.8 + t * 0.12) * 0.5 + Math.sin(nx * 4 - ny * 3 + t * 0.08) * 0.3; const grain = Math.sin(x * 0.75 + fold * 2) * Math.sin(y * 0.18 + t * 0.03); // Luce radente anisotropica const highlight = Math.pow( Math.max(0, Math.cos(folds * 1.8 + grain * 0.15)), 8 ); const shade = Math.sin(folds * 1.4 + fold) * 0.5 + 0.5; // Oro antico, champagne e bronzo let r = 145 + shade * 35 + highlight * 68; let g = 103 + shade * 31 + highlight * 65; let b = 25 + shade * 17 + highlight * 39; const ambient = Math.exp( -( Math.pow((nx - 0.48) / 0.65, 2) + Math.pow((ny - 0.42) / 0.8, 2) ) ); r += ambient * 20; g += ambient * 19; b += ambient * 10; return [ Math.min(255, r), Math.min(255, g), Math.min(255, b) ]; } function draw() { const w = imageData.width; const h = imageData.height; for (let y = 0; y < h; y++) { for (let x = 0; x < w; x++) { const c = velvet(x * 2, y * 2, time); const i = (y * w + x) * 4; pixels[i] = c[0]; pixels[i + 1] = c[1]; pixels[i + 2] = c[2]; pixels[i + 3] = 255; } } // Rendering della materia const temp = document.createElement('canvas'); temp.width = w; temp.height = h; temp.getContext('2d').putImageData(imageData, 0, 0); ctx.drawImage(temp, 0, 0, width, height); // Microtexture del pelo del velluto ctx.save(); ctx.globalAlpha = 0.075; ctx.globalCompositeOperation = 'soft-light'; const offset = time * 2; ctx.translate(offset % 256, offset % 256); ctx.fillStyle = noisePattern; ctx.fillRect(-256, -256, width + 512, height + 512); ctx.restore(); // Luce ambientale diffusa const lightX = width * (0.4 + Math.sin(time * 0.08) * 0.12); const lightY = height * (0.35 + Math.cos(time * 0.06) * 0.1); const light = ctx.createRadialGradient( lightX, lightY, 0, lightX, lightY, Math.max(width, height) * 0.8 ); light.addColorStop(0, 'rgba(255, 224, 150, 0.09)'); light.addColorStop(0.5, 'rgba(212, 175, 85, 0.025)'); light.addColorStop(1, 'rgba(45, 27, 5, 0.13)'); ctx.fillStyle = light; ctx.fillRect(0, 0, width, height); // Movimento estremamente lento time += 0.0015; requestAnimationFrame(draw); } window.addEventListener('resize', resizeCanvas); resizeCanvas(); draw();