cделай сайт живым
Сайты с характером, движением и ощущением присутствия.
There is no design without discipline. There is no discipline without intelligence.
RAIN GLASS EFFECT
Превращает обычный фон в поверхность,
по которой медленно стекают капли.

В скрипте можно поменять цвет фона. Эффект распространяется на весь сайт.
преимущественно для светлого фона
кликните на картинку для демонстрации
<!-- rain glass by kavaevacreate -->


<div id="rain-glass-bg" aria-hidden="true">
<canvas id="rain-glass-canvas"></canvas>
</div>

<style>
#rain-glass-bg{
position:fixed; inset:0; width:100vw; height:100vh;
z-index:0; pointer-events:none; overflow:hidden;
background-color: var(--rain-bg-color, transparent);
background-image:
radial-gradient(ellipse at 20% 15%,rgba(255,255,255,.16),transparent 42%),
radial-gradient(ellipse at 82% 70%,rgba(220,230,240,.10),transparent 48%),
linear-gradient(135deg,rgba(245,247,248,.18),rgba(210,218,224,.10));
}
#rain-glass-canvas{position:absolute;inset:0;width:100%;height:100%;display:block}
@media(prefers-reduced-motion:reduce){#rain-glass-canvas{opacity:.92}}
</style>

<script>
(function(){
'use strict';
const canvas=document.getElementById('rain-glass-canvas');
if(!canvas)return;
const ctx=canvas.getContext('2d',{alpha:true});

const CONFIG={


backgroundColor:'#f7f7f7', // любой цвет фона

fogLayers:7,
fogSpeed:.000035,
dropletCount:105,
minSize:1.2,maxSize:5, // насколько капли вытягиваются (больше 5 ставить не советую)
minSpeed:.18,maxSpeed:.72, // скорость стекания
trailChance:.22,
trailLength:[18,25],
condensationCount:130,
dropOpacity:.70, // прозрачность капель
highlightOpacity:.34,
shadowOpacity:.10,
ringOnlyRadius:2,
jitterMin:.84,jitterMax:1.16,
streakChance:.85,
shadowBlurPx:3.2,
fogUpdateMs:110,
largeSpriteVariants:10, // сколько вариантов капель есть
ringSpriteVariants:4, // сколько вариантов капель есть
spriteBakeRadius:50,
spritePad:24,
};

let W=0,H=0,dpr=1,droplets=[],condensation=[];
let seed=Math.random()*10000,last=0;
const reduced=window.matchMedia&&window.matchMedia('(prefers-reduced-motion: reduce)').matches;

const rand=(a,b)=>a+Math.random()*(b-a);

function resize(){
dpr=Math.min(devicePixelRatio||1,2);
W=innerWidth;H=innerHeight;
canvas.width=Math.round(W*dpr);canvas.height=Math.round(H*dpr);
canvas.style.width=W+'px';canvas.style.height=H+'px';
ctx.setTransform(dpr,0,0,dpr,0,0);
buildCondensation();buildDroplets();
}

function buildCondensation(){
condensation=[];
for(let i=0;i<CONFIG.condensationCount;i++)
condensation.push({x:rand(0,W),y:rand(0,H),r:rand(.5,2.2),a:rand(.018,.065),p:rand(0,Math.PI*2)});
}

function makeJitter(){
const j=[];
for(let i=0;i<8;i++)j.push(rand(CONFIG.jitterMin,CONFIG.jitterMax));
return j;
}

function makeStreaks(){
const streaks=[];
const count=Math.round(rand(2,4));
for(let i=0;i<count;i++)streaks.push({a:rand(-2.3,-0.9),from:rand(.05,.2),len:rand(.35,.75)});
return streaks;
}

function blobPath(jitter,r){
const pts=jitter.length;
const path=new Path2D();
const coords=[];
for(let i=0;i<pts;i++){
const angle=(i/pts)*Math.PI*2;
coords.push([Math.cos(angle)*r*jitter[i], Math.sin(angle)*r*jitter[i]]);
}
path.moveTo((coords[0][0]+coords[pts-1][0])/2,(coords[0][1]+coords[pts-1][1])/2);
for(let i=0;i<pts;i++){
const next=coords[(i+1)%pts];
const mid=[(coords[i][0]+next[0])/2,(coords[i][1]+next[1])/2];
path.quadraticCurveTo(coords[i][0],coords[i][1],mid[0],mid[1]);
}
path.closePath();
return path;
}

let largeSprites=[],ringSprites=[],shadowSprite=null;
const SB=CONFIG.spriteBakeRadius, SP=CONFIG.spritePad, SS=(SB+SP)*2, SC=SB+SP;

function bakeLargeSprite(){
const c=document.createElement('canvas');c.width=SS;c.height=SS;
const sctx=c.getContext('2d');
sctx.translate(SC,SC);
const r=SB;
const jitter=makeJitter();
const streaks=Math.random()<CONFIG.streakChance?makeStreaks():[];
const path=blobPath(jitter,r);

const body=sctx.createRadialGradient(-r*.3,-r*.34,r*.05,0,0,r*1.05);
body.addColorStop(0,'rgba(255,255,255,1)');
body.addColorStop(.32,'rgba(250,253,255,.48)');
body.addColorStop(.7,'rgba(220,230,236,.16)');
body.addColorStop(1,'rgba(205,215,222,0)');
sctx.fillStyle=body;sctx.fill(path);

sctx.beginPath();
sctx.ellipse(0,0,r*.94,r*.94,0,Math.PI*0.15,Math.PI*0.95);
sctx.lineWidth=Math.max(.6,r*.11);
sctx.strokeStyle='rgba(40,50,60,.55)';
sctx.stroke();

sctx.lineWidth=Math.max(.4,r*.04);
sctx.strokeStyle='rgba(255,255,255,.5)';
sctx.stroke(path);

sctx.beginPath();
sctx.ellipse(-r*.32,-r*.36,Math.max(.6,r*.24),Math.max(.4,r*.13),-0.5,0,Math.PI*2);
sctx.fillStyle=`rgba(255,255,255,${CONFIG.highlightOpacity})`;sctx.fill();

sctx.beginPath();
sctx.ellipse(r*.26,r*.14,Math.max(.4,r*.09),Math.max(.3,r*.06),0.4,0,Math.PI*2);
sctx.fillStyle=`rgba(255,255,255,${CONFIG.highlightOpacity*.5})`;sctx.fill();

if(streaks.length){
sctx.strokeStyle='rgba(255,255,255,.9)';
sctx.lineCap='round';
for(const s of streaks){
sctx.lineWidth=Math.max(.4,r*.045);
sctx.beginPath();
sctx.moveTo(Math.cos(s.a)*r*s.from,Math.sin(s.a)*r*s.from);
sctx.lineTo(Math.cos(s.a)*r*s.len,Math.sin(s.a)*r*s.len);
sctx.stroke();
}
}
return c;
}

function bakeRingSprite(){
const c=document.createElement('canvas');c.width=SS;c.height=SS;
const sctx=c.getContext('2d');
sctx.translate(SC,SC);
const r=SB;
const path=blobPath(makeJitter(),r);
sctx.lineWidth=Math.max(.5,r*.28);
sctx.strokeStyle='rgba(255,255,255,1)';
sctx.stroke(path);
sctx.beginPath();sctx.arc(-r*.28,-r*.3,Math.max(.4,r*.32),0,Math.PI*2);
sctx.fillStyle='rgba(255,255,255,1)';sctx.fill();
return c;
}

function bakeShadowSprite(){
const pad=CONFIG.shadowBlurPx*4+10;
const c=document.createElement('canvas');c.width=SS+pad*2;c.height=SS+pad*2;
const sctx=c.getContext('2d');
sctx.translate(SS/2+pad,SS/2+pad);
sctx.filter=`blur(${CONFIG.shadowBlurPx}px)`;
sctx.beginPath();
sctx.ellipse(SB*.12,SB*.22,SB*.94,SB*.98,0,0,Math.PI*2);
sctx.fillStyle='rgba(55,65,75,1)';
sctx.fill();
return {canvas:c,offset:SS/2+pad};
}

function bakeSprites(){
largeSprites=[];for(let i=0;i<CONFIG.largeSpriteVariants;i++)largeSprites.push(bakeLargeSprite());
ringSprites=[];for(let i=0;i<CONFIG.ringSpriteVariants;i++)ringSprites.push(bakeRingSprite());
shadowSprite=bakeShadowSprite();
}

function buildDroplets(){
droplets=[];
for(let i=0;i<CONFIG.dropletCount;i++){
const s=rand(CONFIG.minSize,CONFIG.maxSize),large=s>4.8;
const isRing=s<CONFIG.ringOnlyRadius;
droplets.push({
x:rand(0,W),y:rand(-H,H),size:s,
speed:rand(CONFIG.minSpeed,CONFIG.maxSpeed)*(large?.82:1),
wobble:rand(.04,.22),phase:rand(0,Math.PI*2),
elongation:large?rand(1.7,3.8):rand(1.05,1.8),
trail:large&&Math.random()<CONFIG.trailChance?rand(...CONFIG.trailLength):0,
drift:rand(.05,.20),opacity:rand(CONFIG.dropOpacity*.55,CONFIG.dropOpacity*1.15),
isRing,
spriteIdx:Math.floor(Math.random()*(isRing?CONFIG.ringSpriteVariants:CONFIG.largeSpriteVariants)),
});
}
}

let fogBuf=null,fogBufCtx=null,lastFogUpdate=0;

function renderFogToBuffer(t){
if(!fogBuf){fogBuf=document.createElement('canvas');fogBufCtx=fogBuf.getContext('2d');}
if(fogBuf.width!==canvas.width||fogBuf.height!==canvas.height){
fogBuf.width=canvas.width;fogBuf.height=canvas.height;
fogBufCtx.setTransform(dpr,0,0,dpr,0,0);
}
const fctx=fogBufCtx;
fctx.clearRect(0,0,W,H);
const base=fctx.createLinearGradient(0,0,W,H);
base.addColorStop(0,'rgba(245,248,250,.12)');
base.addColorStop(.45,'rgba(225,232,237,.055)');
base.addColorStop(1,'rgba(245,248,250,.10)');
fctx.fillStyle=base;fctx.fillRect(0,0,W,H);

for(let i=0;i<CONFIG.fogLayers;i++){
const p=seed+i*1.71;
const x=W*(.12+i*.145)+Math.sin(t*CONFIG.fogSpeed*1000+p)*W*.12;
const y=H*(.18+(i%3)*.31)+Math.cos(t*CONFIG.fogSpeed*800+p*.7)*H*.09;
const r=Math.max(W,H)*(.24+Math.random()*.18);
const g=fctx.createRadialGradient(x,y,0,x,y,r);
const a=.018+i*.002;
g.addColorStop(0,`rgba(255,255,255,${a+.018})`);
g.addColorStop(.42,`rgba(235,241,245,${a})`);
g.addColorStop(1,'rgba(235,241,245,0)');
fctx.fillStyle=g;fctx.fillRect(0,0,W,H);
}
}

function drawCondensation(t){
for(const p of condensation){
const r=p.r*(1+Math.sin(t*.00025+p.p)*.1);
ctx.beginPath();ctx.arc(p.x,p.y,r,0,Math.PI*2);
ctx.fillStyle=`rgba(255,255,255,${p.a})`;ctx.fill();
}
}

function dropXY(d,t){
return [d.x+Math.sin(t*.00055+d.phase)*d.drift, d.y, Math.sin(t*.0012*d.wobble+d.phase)*d.wobble];
}

function drawDropShadow(d,t){
const [x,y,wob]=dropXY(d,t);
const rx=d.size,ry=d.size*d.elongation;
ctx.save();
ctx.translate(x,y);ctx.rotate(wob);
ctx.scale(rx/SB,ry/SB);
ctx.globalAlpha=CONFIG.shadowOpacity*(d.size/7);
ctx.drawImage(shadowSprite.canvas,-shadowSprite.offset,-shadowSprite.offset);
ctx.restore();
}

function drawDropBody(d,t){
const [x,y,wob]=dropXY(d,t);
const rx=d.size,ry=d.size*d.elongation;
const sprite=(d.isRing?ringSprites:largeSprites)[d.spriteIdx];

ctx.save();
ctx.translate(x,y);ctx.rotate(wob);
ctx.scale(rx/SB,ry/SB);
ctx.globalAlpha=d.isRing?Math.min(1,d.opacity*1.2):Math.min(1,d.opacity*1.05);
ctx.drawImage(sprite,-SC,-SC);
ctx.restore();

if(d.trail>0){
ctx.save();
ctx.globalAlpha=1;
ctx.translate(x,y);
const trail=ctx.createLinearGradient(0,ry*.45,0,ry*.45+d.trail);
trail.addColorStop(0,`rgba(230,238,243,${d.opacity*.34})`);
trail.addColorStop(.35,`rgba(225,234,240,${d.opacity*.12})`);
trail.addColorStop(1,'rgba(225,234,240,0)');
ctx.beginPath();
ctx.moveTo(-rx*.28,ry*.48);
ctx.quadraticCurveTo(rx*.05,ry*.72+d.trail*.45,rx*.02,ry*.62+d.trail);
ctx.lineWidth=Math.max(.7,rx*.24);ctx.strokeStyle=trail;ctx.stroke();
ctx.restore();
}
}

function update(dt){
if(reduced)return;
for(const d of droplets){
d.y+=d.speed*dt*.06;
if(d.y-d.size*d.elongation>H+20){
d.y=-rand(20,H*.35);d.x=rand(0,W);
d.spriteIdx=Math.floor(Math.random()*(d.isRing?CONFIG.ringSpriteVariants:CONFIG.largeSpriteVariants));
}
}
}

function render(now){
const dt=Math.min(40,now-(last||now));last=now;
ctx.clearRect(0,0,W,H);

if(now-lastFogUpdate>=CONFIG.fogUpdateMs){
renderFogToBuffer(now);
lastFogUpdate=now;
}
if(fogBuf)ctx.drawImage(fogBuf,0,0,fogBuf.width,fogBuf.height,0,0,W,H);

drawCondensation(now);

for(const d of droplets)drawDropShadow(d,now);
for(const d of droplets)drawDropBody(d,now);

update(dt);
requestAnimationFrame(render);
}

bakeSprites();
document.getElementById('rain-glass-bg').style.setProperty('--rain-bg-color', CONFIG.backgroundColor);
resize();
addEventListener('resize',resize,{passive:true});
requestAnimationFrame(render);
})();
</script>
soft fog EFFECT
Добавляет плавающие клубы тумана внизу экрана поверх контента. Все взаимодействия с контентом остаются активными, так как  туман существует сам по себе.
преимущественно для тёмного фона
кликните на картинку для демонстрации
<!-- soft fog by kavaevacreate -->


<div id="atm-fog" style="position:fixed; inset:0; pointer-events:none; z-index:10; overflow:hidden;">
<canvas id="atm-fog-canvas" style="position:absolute; inset:0; width:100%; height:100%; display:block;"></canvas>
</div>

<script>
(function () {
const CONFIG = {
fogColor: [204, 203, 193], // RGB цвета тумана — поменяй под свой сайт

baseOpacity: 0.90, // общая непрозрачность тумана

densityLow: 0.40, // насколько "рваный" туман: чем БЛИЖЕ друг к другу эти два числа, тем больше "дыр" между ними
densityHigh: 0.78,

groundBias: 0.65, // >1 = туман сильнее прижат к низу экрана, <1 = более равномерный по высоте
groundFloor: 0.12,

octaves: 3, // слоёв детализации шума
noiseScale: 2.6, // масштаб клубов: больше = клубы мельче и их больше
warpAmount: 1.15, // сила завихрений

driftSpeed: 0.04, // скорость изменения формы
swirlRadius: 0.55,

bufferWidth: 192,
updateMs: 55,

mobile: {
breakpoint: 768,
baseOpacity: 1,
octaves: 5,
bufferWidth: 148,
updateMs: 75,
},
};


function hash(x, y) {
const n = Math.sin(x * 127.1 + y * 311.7) * 43758.5453123;
return n - Math.floor(n);
}
function smooth(t) { return t * t * (3 - 2 * t); }

function valueNoise(x, y) {
const xi = Math.floor(x), yi = Math.floor(y);
const xf = x - xi, yf = y - yi;
const a = hash(xi, yi), b = hash(xi + 1, yi);
const c = hash(xi, yi + 1), d = hash(xi + 1, yi + 1);
const u = smooth(xf), v = smooth(yf);
return a + (b - a) * u + (c - a) * v + (a - b - c + d) * u * v;
}

function fbm(x, y, octaves) {
let total = 0, amp = 0.5, freq = 1, max = 0;
for (let i = 0; i < octaves; i++) {
total += valueNoise(x * freq, y * freq) * amp;
max += amp;
amp *= 0.52;
freq *= 2.12;
}
return total / max;
}

const root = document.getElementById('atm-fog');
const canvas = document.getElementById('atm-fog-canvas');
const ctx = canvas.getContext('2d');

const buffer = document.createElement('canvas');
const bctx = buffer.getContext('2d');

let W = 0, H = 0, bufW = 0, bufH = 0;
let cfg = { ...CONFIG };
const prefersReduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;

function applyResponsiveConfig() {
const isMobile = window.innerWidth <= CONFIG.mobile.breakpoint;
cfg = isMobile ? { ...CONFIG, ...CONFIG.mobile } : { ...CONFIG };
}

function resize() {
applyResponsiveConfig();
const rect = root.getBoundingClientRect();
W = Math.max(1, rect.width);
H = Math.max(1, rect.height);

canvas.width = W;
canvas.height = H;

bufW = cfg.bufferWidth;
bufH = Math.max(1, Math.round(bufW * (H / W)));
buffer.width = bufW;
buffer.height = bufH;
}

const imgData_cache = {};
function getImageData() {
if (!imgData_cache.data || imgData_cache.w !== bufW || imgData_cache.h !== bufH) {
imgData_cache.data = bctx.createImageData(bufW, bufH);
imgData_cache.w = bufW;
imgData_cache.h = bufH;
}
return imgData_cache.data;
}

function renderNoiseFrame(t) {
const img = getImageData();
const data = img.data;
const [rC, gC, bC] = cfg.fogColor;

const ox = Math.sin(t * cfg.driftSpeed) * cfg.swirlRadius;
const oy = Math.cos(t * cfg.driftSpeed * 0.8) * cfg.swirlRadius * 0.6;
const wt = t * cfg.driftSpeed * 1.6;

for (let py = 0; py < bufH; py++) {
const ny = py / bufH;
const groundMask = cfg.groundFloor + (1 - cfg.groundFloor) * Math.pow(ny, cfg.groundBias);

for (let px = 0; px < bufW; px++) {
const nx = (px / bufW) * cfg.noiseScale;
const nyv = (py / bufH) * cfg.noiseScale * (bufH / bufW);

const wx = fbm(nx * 0.7 + Math.sin(wt) * 1.3, nyv * 0.7 + Math.cos(wt * 0.9) * 1.3, 2);
const wy = fbm(nx * 0.7 + Math.cos(wt * 1.1) * 1.3, nyv * 0.7 + Math.sin(wt * 1.2) * 1.3, 2);

const sx = nx + ox + (wx - 0.5) * cfg.warpAmount;
const sy = nyv + oy + (wy - 0.5) * cfg.warpAmount;

const density = fbm(sx, sy, cfg.octaves) * groundMask;

let a = (density - cfg.densityLow) / (cfg.densityHigh - cfg.densityLow);
a = Math.max(0, Math.min(1, a));
a = a * a * (3 - 2 * a);

const idx = (py * bufW + px) * 4;
data[idx] = rC;
data[idx + 1] = gC;
data[idx + 2] = bC;
data[idx + 3] = Math.round(a * 255 * cfg.baseOpacity);
}
}

bctx.putImageData(img, 0, 0);
ctx.clearRect(0, 0, W, H);
ctx.imageSmoothingEnabled = true;
ctx.drawImage(buffer, 0, 0, bufW, bufH, 0, 0, W, H);
}

let lastUpdate = 0;
let tClock = 0;

function loop(now) {
if (now - lastUpdate >= cfg.updateMs) {
tClock += (now - lastUpdate) / 1000;
lastUpdate = now;
renderNoiseFrame(tClock);
}
if (!prefersReduced) requestAnimationFrame(loop);
}

window.addEventListener('resize', resize);
resize();
renderNoiseFrame(0);

if (!prefersReduced) requestAnimationFrame(loop);
})();
</script>
light rays EFFECT
Световые лучи мягко появляются и перемещаются по экрану как естественный свет. Добавляет глубину и ощущение объёма даже статичному фону.
преимущественно для тёмного фона
кликните на картинку для демонстрации
<!-- light rays by kavaevacreate -->


<svg width="0" height="0" style="position:absolute;overflow:hidden;">
<defs>
<filter id="atm-dapple" x="-20%" y="-20%" width="140%" height="140%">
<feTurbulence type="fractalNoise" baseFrequency="0.011 0.017" numOctaves="2" seed="7" result="noise" />
<feColorMatrix in="noise" type="matrix"
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 3.4 -0.75" result="alphaNoise" />
<feComposite in="SourceGraphic" in2="alphaNoise" operator="in" />
</filter>
</defs>
</svg>

<div id="atm-glow" style="position:fixed; inset:0; pointer-events:none; z-index:2147483645; overflow:hidden;">
<div class="atm-glow__ray atm-glow__ray--1"></div>
<div class="atm-glow__ray atm-glow__ray--2"></div>
<div class="atm-glow__ray atm-glow__ray--3"></div>
<canvas id="atm-glow-dust" style="position:absolute; inset:0; width:100%; height:100%; display:block;"></canvas>
</div>

<style>
:root {
--glow-color: 255, 205, 140;
--glow-opacity: 0.43; /* прозрачность лучей */
--glow-speed: 10s; /* скорость лучей */
}

.atm-glow__ray {
position: absolute;
top: -20%;
transform-origin: center top;
background: linear-gradient(
to bottom,
rgba(var(--glow-color), calc(var(--glow-opacity) * 1.8)) 0%,
rgba(var(--glow-color), calc(var(--glow-opacity) * 0.9)) 25%,
rgba(var(--glow-color), calc(var(--glow-opacity) * 0.35)) 55%,
rgba(var(--glow-color), calc(var(--glow-opacity) * 0.08)) 80%,
transparent 100%
);

filter: url(#atm-dapple) blur(10px);
animation: rayPulse var(--glow-speed) ease-in-out infinite;
will-change: transform, opacity;
transform: translateZ(0) rotate(var(--r));
-webkit-backface-visibility: hidden;

-webkit-mask-image: linear-gradient(to right, transparent 0%, black 30%, black 70%, transparent 100%);
mask-image: linear-gradient(to right, transparent 0%, black 30%, black 70%, transparent 100%);
}

/* настройка лучей */
.atm-glow__ray--1 {
width: 26vw;
height: 150vh;
right: 6%;
--r: -26deg;
}
.atm-glow__ray--2 {
width: 26vw;
height: 135vh;
right: 28%;
opacity: 0.75;
filter: url(#atm-dapple) blur(13px);
animation-duration: calc(var(--glow-speed) * 1.15);
animation-delay: -3.5s;
--r: -13deg;
}
.atm-glow__ray--3 {
width: 34vw;
height: 140vh;
right: -8%;
opacity: 0.5;
filter: url(#atm-dapple) blur(18px);
animation-duration: calc(var(--glow-speed) * 1.35);
animation-delay: -6s;
--r: -36deg;
}

@keyframes rayPulse {
0% { opacity: 0.62; transform: translateZ(0) scaleY(1) rotate(var(--r)); }
25% { opacity: 1; transform: translateZ(0) scaleY(1.05) rotate(calc(var(--r) + 0.4deg)); }
50% { opacity: 0.78; transform: translateZ(0) scaleY(1.02) rotate(var(--r)); }
75% { opacity: 0.95; transform: translateZ(0) scaleY(1.06) rotate(calc(var(--r) - 0.4deg)); }
100% { opacity: 0.62; transform: translateZ(0) scaleY(1) rotate(var(--r)); }
}

@media (max-width: 768px) {
:root {
--glow-opacity: 0.5;
--glow-speed: 14s;
}

.atm-glow__ray,
.atm-glow__ray--2,
.atm-glow__ray--3 {
filter: blur(11px);
}
}

@media (prefers-reduced-motion: reduce) {
.atm-glow__ray { animation: none; }
}
</style>

<script>
(function () {
const CONFIG = {
dustColor: [255, 238, 212],
dustCountPerMillionPx: 40,
dustMin: 18,
dustMax: 85,

/* настройка пылинок */
sizeMin: 0.3,
sizeMax: 1,
softness: 2, // меньше = резче точка
opacityMin: 0.18,
opacityMax: 0.6,

wanderSpeed: 3.5, // px/сек
wanderJitter: 0.9, // насколько резко меняется направление
maxSpeed: 9,

twinkleSpeed: [0.4, 1.1],

rays: [
{ xFrac: 0.71, widthFrac: 0.24, angleDeg: -26, weight: 1.0 },
{ xFrac: 0.59, widthFrac: 0.14, angleDeg: -13, weight: 0.7 },
{ xFrac: 0.90, widthFrac: 0.18, angleDeg: -36, weight: 0.55 },
],

mobile: {
breakpoint: 768,
dustCountPerMillionPx: 36,
dustMax: 95,
},
};

const canvas = document.getElementById('atm-glow-dust');
const ctx = canvas.getContext('2d');
const DPR = Math.min(window.devicePixelRatio || 1, 2);
const prefersReduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;

let W = 0, H = 0;
let dust = [];
let activeCfg = CONFIG;

function rand(min, max) { return min + Math.random() * (max - min); }

function pickRay() {
const totalWeight = activeCfg.rays.reduce((s, r) => s + r.weight, 0);
let roll = Math.random() * totalWeight;
for (const r of activeCfg.rays) {
if (roll < r.weight) return r;
roll -= r.weight;
}
return activeCfg.rays[0];
}

function makeDust() {
const ray = pickRay();
const y = rand(0, H);
const angleRad = (ray.angleDeg * Math.PI) / 180;
const centerX = ray.xFrac * W + Math.tan(angleRad) * (y - H * 0.2);
const spread = ray.widthFrac * W * 0.5;

const dir = rand(0, Math.PI * 2);
return {
x: centerX + rand(-spread, spread),
y: y,
vx: Math.cos(dir) * activeCfg.wanderSpeed * 0.4,
vy: Math.sin(dir) * activeCfg.wanderSpeed * 0.4,
size: rand(activeCfg.sizeMin, activeCfg.sizeMax),
baseOpacity: rand(activeCfg.opacityMin, activeCfg.opacityMax),
phase: rand(0, Math.PI * 2),
twinkleSpeed: rand(activeCfg.twinkleSpeed[0], activeCfg.twinkleSpeed[1]),
life: rand(9, 22), // сек до плавного возврата в луч
age: 0,
};
}

function targetDustCount() {
const n = Math.round((W * H) / 1000000 * activeCfg.dustCountPerMillionPx);
return Math.max(activeCfg.dustMin, Math.min(activeCfg.dustMax, n));
}

function resize() {
const isMobile = window.innerWidth <= CONFIG.mobile.breakpoint;
activeCfg = isMobile ? { ...CONFIG, ...CONFIG.mobile } : CONFIG;

W = window.innerWidth;
H = window.innerHeight;
canvas.width = W * DPR;
canvas.height = H * DPR;
ctx.setTransform(DPR, 0, 0, DPR, 0, 0);

const target = targetDustCount();
if (dust.length < target) {
const toAdd = target - dust.length;
for (let i = 0; i < toAdd; i++) dust.push(makeDust());
} else if (dust.length > target) {
dust.length = target;
}
}

function update(dt) {
for (let i = 0; i < dust.length; i++) {
const p = dust[i];
p.age += dt;

p.vx += rand(-activeCfg.wanderJitter, activeCfg.wanderJitter) * dt;
p.vy += rand(-activeCfg.wanderJitter, activeCfg.wanderJitter) * dt;

const speed = Math.sqrt(p.vx * p.vx + p.vy * p.vy);
if (speed > activeCfg.maxSpeed) {
p.vx = (p.vx / speed) * activeCfg.maxSpeed;
p.vy = (p.vy / speed) * activeCfg.maxSpeed;
}

p.x += p.vx * dt * 10;
p.y += p.vy * dt * 10;

const offscreen = p.x < -60 || p.x > W + 60 || p.y < -60 || p.y > H + 60;
if (offscreen || p.age > p.life) {
dust[i] = makeDust();
}
}
}

function render(t) {
ctx.clearRect(0, 0, W, H);
const rC = activeCfg.dustColor[0], gC = activeCfg.dustColor[1], bC = activeCfg.dustColor[2];
for (const p of dust) {
const twinkle = 0.5 + 0.5 * Math.sin(t * p.twinkleSpeed + p.phase);
const alpha = p.baseOpacity * (0.45 + 0.55 * twinkle);
const r = p.size * activeCfg.softness;

const grad = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, r);
grad.addColorStop(0, `rgba(${rC},${gC},${bC},${alpha})`);
grad.addColorStop(0.55, `rgba(${rC},${gC},${bC},${alpha * 0.5})`);
grad.addColorStop(1, `rgba(${rC},${gC},${bC},0)`);
ctx.fillStyle = grad;
ctx.beginPath();
ctx.arc(p.x, p.y, r, 0, Math.PI * 2);
ctx.fill();
}
}

let last = performance.now();
function loop(now) {
const dt = Math.min(0.05, (now - last) / 1000);
last = now;
update(dt);
render(now / 1000);
requestAnimationFrame(loop);
}

window.addEventListener('resize', resize);
resize();

if (prefersReduced) {
render(0);
} else {
requestAnimationFrame(loop);
}
})();
</script>
shadow leaves EFFECT
Живые тени листьев медленно колышатся по поверхности экрана. Эффект поверх контента.
преимущественно для светлого фона
кликните на картинку для демонстрации
<!-- shadow leaves by kavaevacreate -->

<style>
:root {
--leaves-opacity: 0.4;
--palm-opacity: 0.15;
}

.atm-leaves {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 10;
overflow: hidden;
}

.atm-leaves__layer {
position: absolute;
top: -10%;
left: -10%;
width: 120%;
height: 120%;
background-repeat: no-repeat;
background-position: center;
background-size: cover;
will-change: transform;
}

.atm-leaves__layer--leaf {
opacity: var(--leaves-opacity);
background-image: url('https://res.cloudinary.com/mmfwrpyg/image/upload/v1786483227/LEAF_DESKTOP.png');
animation: leafDrift 7s ease-in-out infinite;
}


.atm-leaves__layer--palm {
opacity: var(--palm-opacity);
background-image: url('https://res.cloudinary.com/mmfwrpyg/image/upload/v1786483227/PALM_DESKTOP.png');
animation: palmDrift 10s ease-in-out infinite;
}

@keyframes leafDrift {
0% { transform: translate(0, 0) scale(1) rotate(0deg); } /* движение листьев по шагам*/
50% { transform: translate(1%, 3.2%) scale(1.015) rotate(-0.3deg); }
100% { transform: translate(0, 0) scale(1) rotate(0deg); }
}

@keyframes palmDrift {
0% { transform: translate(0, 0) scale(1) rotate(0deg); } /* движение пальмы по шагам*/
30% { transform: translate(-1.8%, 1.4%) scale(1.02) rotate(-0.4deg); }
55% { transform: translate(-3%, -0.6%) scale(1.03) rotate(0.3deg); }
80% { transform: translate(-0.8%, -1.6%) scale(1.01) rotate(0.5deg); }
100% { transform: translate(0, 0) scale(1) rotate(0deg); }
}

@media (max-width: 768px) {
:root {
--leaves-opacity: 0.4; /* прозрачность листьев сверху слева мобилка*/
--palm-opacity: 0.4; /* прозрачность пальмы снизу мобилка */
}
.atm-leaves__layer--leaf {
background-image: url('https://res.cloudinary.com/mmfwrpyg/image/upload/v1786483227/LEAF_MOBILE.png');
animation-duration: 4s;
}
.atm-leaves__layer--palm {
background-image: url('https://res.cloudinary.com/mmfwrpyg/image/upload/v1786483227/PALM_MOBILE.png');
animation-duration: 7s;
}
}

@media (prefers-reduced-motion: reduce) {
.atm-leaves__layer { animation: none; }
}
</style>

<script>
(function () {
document.querySelectorAll('.atm-leaves').forEach(function (el) { el.remove(); });

var wrap = document.createElement('div');
wrap.className = 'atm-leaves';

var leafLayer = document.createElement('div');
leafLayer.className = 'atm-leaves__layer atm-leaves__layer--leaf';

var palmLayer = document.createElement('div');
palmLayer.className = 'atm-leaves__layer atm-leaves__layer--palm';

wrap.appendChild(leafLayer);
wrap.appendChild(palmLayer);
document.body.appendChild(wrap);
})();
</script>
film burn EFFECT
Мягкие засветки и вспышки света появляются поверх изображения, создавая ощущение аналоговой плёнки. Добавляет кадру тепло, движение и лёгкую кинематографичность.
преимущественно для тёмного фона
кликните на картинку для демонстрации
<!-- film burn by kavaevacreate -->

<div id="atm-filmburn" style="position:fixed; inset:0; pointer-events:none; z-index:2147483643; overflow:hidden;">
<canvas id="atm-filmburn-canvas" style="position:absolute; inset:0; width:100%; height:100%; display:block;"></canvas>
</div>

<script>
(function () {

const CONFIG = {
/*side: направление в градусах, откуда идёт засветк*/
/* (0 = слева, 90 = сверху, 180 = справа, 270 = снизу*/
leaks: [
{ side: 0, reach: 0.34, softness: 0.22, color: [235, 120, 40], opacity: 0.55, drift: 0.015,
flash: true, flashChancePerSec: 0.28, flashPeak: 2.0, flashDecay: 0.90 },
{ side: 205, reach: 0.30, softness: 0.26, color: [235, 70, 60], opacity: 0.35, drift: 0.02,
flash: true, flashChancePerSec: 0.22, flashPeak: 2.3, flashDecay: 0.88 },
{ side: 60, reach: 0.22, softness: 0.30, color: [70, 160, 175], opacity: 0.22, drift: 0.012,
flash: false, flashChancePerSec: 0, flashPeak: 1, flashDecay: 0.9 },
],
leakNoiseAmount: 0.26, // насколько неровная граница засветки
leakPatchiness: 0.45, // неравномерность плотности внутри засветки

/*перфорация плёнки сверху и снизу*/
sprockets: {
enabled: true,
barHeightFrac: 0.062, // высота тёмной полосы, доля от высоты экрана (vh)
holeWidthFrac: 0.046, // ширина одного отверстия, доля от ширины экрана (vw)
gapFrac: 0.006, // расстояние между отверстиями
cornerRadius: 4,
barColor: 'rgba(8, 7, 6, 0.86)',
scrollSpeed: 16, // px/сек — скорость "прокрутки плёнки". Отрицательное = в другую сторону
},

/*зерно плёнки */
grainOpacity: 0.10,
grainTileSize: 96,
grainTileCount: 6,
grainChangeMs: 90, /*(мерцание зерна) */

/* царапины */
scratchChancePerSec: 0.5, // сколько новых царапин в среднем в секунду
scratchMaxActive: 3,
scratchOpacity: [0.05, 0.16],
scratchLifeMs: [220, 650],
scratchColor: [255, 255, 255],

/*пылинки на плёнке*/
dustCountPerMillionPx: 55,
dustMin: 24,
dustMax: 110,
dustSize: [0.5, 1.6],
dustOpacity: [0.12, 0.4],
dustJitter: 0.25, // почти неподвижны

bufferWidth: 176,
leakUpdateMs: 70,

mobile: {
breakpoint: 768,
bufferWidth: 120,
grainTileSize: 72,
dustCountPerMillionPx: 34,
dustMax: 65,
scratchMaxActive: 2,
},
};

function hash(x, y) {
const n = Math.sin(x * 127.1 + y * 311.7) * 43758.5453123;
return n - Math.floor(n);
}
function smooth(t) { return t * t * (3 - 2 * t); }
function valueNoise(x, y) {
const xi = Math.floor(x), yi = Math.floor(y);
const xf = x - xi, yf = y - yi;
const a = hash(xi, yi), b = hash(xi + 1, yi);
const c = hash(xi, yi + 1), d = hash(xi + 1, yi + 1);
const u = smooth(xf), v = smooth(yf);
return a + (b - a) * u + (c - a) * v + (a - b - c + d) * u * v;
}
function fbm(x, y, octaves) {
let total = 0, amp = 0.5, freq = 1, max = 0;
for (let i = 0; i < octaves; i++) {
total += valueNoise(x * freq, y * freq) * amp;
max += amp; amp *= 0.53; freq *= 2.1;
}
return total / max;
}
function clamp01(v) { return Math.max(0, Math.min(1, v)); }
function smoothstep(e0, e1, x) {
const t = clamp01((x - e0) / (e1 - e0));
return t * t * (3 - 2 * t);
}


const canvas = document.getElementById('atm-filmburn-canvas');
const ctx = canvas.getContext('2d');
const leakBuf = document.createElement('canvas');
const leakCtx = leakBuf.getContext('2d');

const DPR = Math.min(window.devicePixelRatio || 1, 2);
const prefersReduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;

let W = 0, H = 0, bufW = 0, bufH = 0;
let cfg = CONFIG;
let dust = [];
let scratches = [];
let grainTiles = [];
let grainIndex = 0;
let lastGrainSwitch = 0;
let lastLeakUpdate = 0;
let leakFlash = [];

function rand(min, max) { return min + Math.random() * (max - min); }

function applyResponsive() {
const isMobile = window.innerWidth <= CONFIG.mobile.breakpoint;
cfg = isMobile ? { ...CONFIG, ...CONFIG.mobile } : CONFIG;
if (leakFlash.length !== cfg.leaks.length) {
leakFlash = cfg.leaks.map(() => 0);
}
}

function updateFlashes(dt) {
cfg.leaks.forEach((leak, i) => {
leakFlash[i] *= Math.pow(leak.flashDecay, dt * 60);
if (leak.flash && Math.random() < leak.flashChancePerSec * dt) {
leakFlash[i] = 1;
}
});
}

function buildGrainTiles() {
grainTiles = [];
const size = cfg.grainTileSize;
for (let i = 0; i < cfg.grainTileCount; i++) {
const t = document.createElement('canvas');
t.width = size; t.height = size;
const tctx = t.getContext('2d');
const img = tctx.createImageData(size, size);
for (let p = 0; p < img.data.length; p += 4) {
const v = Math.random() * 255;
img.data[p] = v; img.data[p + 1] = v; img.data[p + 2] = v;
img.data[p + 3] = Math.random() * 255;
}
tctx.putImageData(img, 0, 0);
grainTiles.push(t);
}
}

function makeDust() {
return {
x: rand(0, W),
y: rand(0, H),
size: rand(cfg.dustSize[0], cfg.dustSize[1]),
baseOpacity: rand(cfg.dustOpacity[0], cfg.dustOpacity[1]),
phase: rand(0, Math.PI * 2),
twinkleSpeed: rand(0.3, 1.0),
jx: rand(-1, 1), jy: rand(-1, 1),
};
}

function targetDustCount() {
const n = Math.round((W * H) / 1000000 * cfg.dustCountPerMillionPx);
return Math.max(cfg.dustMin, Math.min(cfg.dustMax, n));
}

function resize() {
applyResponsive();
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W * DPR; canvas.height = H * DPR;
ctx.setTransform(DPR, 0, 0, DPR, 0, 0);

bufW = cfg.bufferWidth;
bufH = Math.max(1, Math.round(bufW * (H / W)));
leakBuf.width = bufW; leakBuf.height = bufH;

buildGrainTiles();

const target = targetDustCount();
if (dust.length < target) {
for (let i = 0; i < target - dust.length; i++) dust.push(makeDust());
} else if (dust.length > target) {
dust.length = target;
}
}

function renderLeaks(t) {
const img = leakCtx.createImageData(bufW, bufH);
const data = img.data;

const flashMults = cfg.leaks.map((leak, i) => 1 + leakFlash[i] * (leak.flashPeak - 1));

for (let py = 0; py < bufH; py++) {
const ny = py / bufH;
for (let px = 0; px < bufW; px++) {
const nx = px / bufW;
let rSum = 0, gSum = 0, bSum = 0, aSum = 0;

for (let li = 0; li < cfg.leaks.length; li++) {
const leak = cfg.leaks[li];
const flashMult = flashMults[li];

const ang = (leak.side * Math.PI) / 180;
const edgeDist = 0.5 - (Math.cos(ang) * (nx - 0.5) + Math.sin(ang) * (ny - 0.5));

const wob = fbm(nx * 2.6 + t * leak.drift, ny * 2.6 + t * leak.drift * 0.7, 2);
const perturbedReach = leak.reach + (wob - 0.5) * CONFIG.leakNoiseAmount;

let density = 1 - smoothstep(perturbedReach - leak.softness, perturbedReach + leak.softness, edgeDist);
if (density <= 0.002) continue;

const patch = fbm(nx * 5 + t * 0.05, ny * 5 - t * 0.04, 3);
density *= 1 - CONFIG.leakPatchiness + CONFIG.leakPatchiness * patch;

const a = Math.min(1, density * leak.opacity * flashMult);
rSum += leak.color[0] * a;
gSum += leak.color[1] * a;
bSum += leak.color[2] * a;
aSum += a;
}

const idx = (py * bufW + px) * 4;
if (aSum > 0.002) {
data[idx] = Math.min(255, rSum / aSum);
data[idx + 1] = Math.min(255, gSum / aSum);
data[idx + 2] = Math.min(255, bSum / aSum);
data[idx + 3] = Math.round(clamp01(aSum) * 255);
}
}
}
leakCtx.putImageData(img, 0, 0);
}


function spawnScratch() {
scratches.push({
x: rand(0, W),
skew: rand(-14, 14),
width: rand(0.5, 1.6),
opacity: rand(cfg.scratchOpacity[0], cfg.scratchOpacity[1]),
born: performance.now(),
life: rand(cfg.scratchLifeMs[0], cfg.scratchLifeMs[1]),
segLen: rand(H * 0.3, H),
segY: rand(0, H * 0.6),
});
}

function drawScratches(now) {
scratches = scratches.filter(s => now - s.born < s.life);
const [rC, gC, bC] = cfg.scratchColor;
for (const s of scratches) {
const age = (now - s.born) / s.life;
const fade = Math.sin(age * Math.PI);
ctx.strokeStyle = `rgba(${rC},${gC},${bC},${s.opacity * fade})`;
ctx.lineWidth = s.width;
ctx.beginPath();
ctx.moveTo(s.x, s.segY);
ctx.lineTo(s.x + s.skew, s.segY + s.segLen);
ctx.stroke();
}
}

function roundedRectPath(c, x, y, w, h, r) {
c.beginPath();
c.moveTo(x + r, y);
c.arcTo(x + w, y, x + w, y + h, r);
c.arcTo(x + w, y + h, x, y + h, r);
c.arcTo(x, y + h, x, y, r);
c.arcTo(x, y, x + w, y, r);
c.closePath();
}

function drawSprocketBar(barY, t) {
const s = cfg.sprockets;
const barH = H * s.barHeightFrac;
const holeW = W * s.holeWidthFrac;
const gap = W * s.gapFrac;
const holeH = barH * 0.52;
const holeY = barY + (barH - holeH) / 2;
const step = holeW + gap;

ctx.save();
ctx.fillStyle = s.barColor;
ctx.fillRect(0, barY, W, barH);

ctx.globalCompositeOperation = 'destination-out';
ctx.fillStyle = 'rgba(0,0,0,1)';
const baseOffset = (W % step) / 2;
const scrollPx = t * s.scrollSpeed;
const shift = ((scrollPx % step) + step) % step;
for (let x = -step + baseOffset - shift; x < W + step; x += step) {
roundedRectPath(ctx, x, holeY, holeW, holeH, s.cornerRadius);
ctx.fill();
}
ctx.restore();
}

function drawSprockets(t) {
if (!cfg.sprockets.enabled) return;
drawSprocketBar(0, t);
drawSprocketBar(H - H * cfg.sprockets.barHeightFrac, t);
}

function drawGrain(now) {
if (now - lastGrainSwitch > cfg.grainChangeMs) {
grainIndex = (grainIndex + Math.ceil(Math.random() * 2)) % grainTiles.length;
lastGrainSwitch = now;
}
const tile = grainTiles[grainIndex];
const pattern = ctx.createPattern(tile, 'repeat');
ctx.save();
ctx.globalAlpha = cfg.grainOpacity;
ctx.globalCompositeOperation = 'overlay';
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, W, H);
ctx.restore();
}

function drawDust(now) {
const t = now / 1000;
ctx.save();
for (const p of dust) {
const jx = Math.sin(t * 0.6 + p.phase) * cfg.dustJitter * p.jx;
const jy = Math.cos(t * 0.5 + p.phase) * cfg.dustJitter * p.jy;
const twinkle = 0.5 + 0.5 * Math.sin(t * p.twinkleSpeed + p.phase);
const alpha = p.baseOpacity * (0.4 + 0.6 * twinkle);
ctx.fillStyle = `rgba(255,255,255,${alpha})`;
ctx.beginPath();
ctx.arc(p.x + jx, p.y + jy, p.size, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
}

let lastFrame = performance.now();
let tClock = 0;

function loop(now) {
const dt = Math.min(0.05, (now - lastFrame) / 1000);
lastFrame = now;
tClock += dt;

updateFlashes(dt);

if (now - lastLeakUpdate > cfg.leakUpdateMs) {
renderLeaks(tClock);
lastLeakUpdate = now;
}

if (Math.random() < cfg.scratchChancePerSec * dt && scratches.length < cfg.scratchMaxActive) {
spawnScratch();
}

ctx.clearRect(0, 0, W, H);

ctx.save();
ctx.globalCompositeOperation = 'screen';
ctx.imageSmoothingEnabled = true;
ctx.drawImage(leakBuf, 0, 0, bufW, bufH, 0, 0, W, H);
ctx.restore();

drawGrain(now);
drawScratches(now);
drawDust(now);
drawSprockets(tClock);

if (!prefersReduced) requestAnimationFrame(loop);
}

window.addEventListener('resize', resize);
resize();
renderLeaks(0);

if (!prefersReduced) {
requestAnimationFrame(loop);
} else {
ctx.save();
ctx.globalCompositeOperation = 'screen';
ctx.drawImage(leakBuf, 0, 0, bufW, bufH, 0, 0, W, H);
ctx.restore();
drawGrain(performance.now());
drawDust(performance.now());
drawSprockets(0);
}
})();
</script>
white noise EFFECT
Помогает избавиться от ощущения слишком «идеального» digital-визуала и добавить характер.
преимущественно для тёмного фона
кликните на картинку для демонстрации
<!-- white noise by kavaevacreate -->

<style>
:root {
--noise-opacity: 0.4; /* сила шума. Советую 0.4 - 0.5 максимум */
--noise-size: 120px; /* Размер лучше регулировать в пределах референтного значения +- 20*/
}

.atm-noise {
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100vw !important;
height: 100vh !important;
pointer-events: none !important;
z-index: 10 !important;
opacity: var(--noise-opacity);
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='5' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.75 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
background-repeat: repeat;
background-size: var(--noise-size) var(--noise-size);

}
</style>

<script>
(function() {
if (document.querySelector('.atm-noise')) return;

const noise = document.createElement('div');
noise.className = 'atm-noise';

document.body.appendChild(noise);

noise.style.zIndex = '10';

let x = 0;
let y = 0;
setInterval(() => {
x = Math.floor(Math.random() * 80);
y = Math.floor(Math.random() * 80);
noise.style.backgroundPosition = `${x}px ${y}px`;
}, 110);
})();
</script>
aurora shine EFFECT
Световые лучи мягко появляются и перемещаются по экрану как естественный свет. Добавляет глубину и ощущение объёма даже статичному фону.
для любого фона
кликните на картинку для демонстрации
<!-- aurora shine by kavaevacreate -->


<div id="atm-aurora" style="position:fixed; inset:0; pointer-events:none; z-index:2147483642; overflow:hidden;">
<canvas id="atm-aurora-canvas" style="position:absolute; inset:0; width:100%; height:100%; display:block; transform:scale(1.01);"></canvas>
</div>

<script>
(function () {
const CONFIG = {
bufferWidth: 300,
updateMs: 70,


opacityMultiplier: 0.4, // общая прозрачность эффекта. 1.5 = плотнее, 0.4 = бледнее
blurPx: 8, // "чёткость". Меньше = резче/чётче узор, больше = мягче/размытее

// преломление
foldScale: 3.3, // масштаб складок: больше = мельче
foldOctaves: 4,
warpAmount: 0.95, // сила искажения (текучесть)
driftSpeed: 0.1, // скорость перетекания

// цвет (голографическая палитра)
saturation: [12, 28], // мин/макс насыщенность
lightness: [74, 92], // мин/макс светлота
hueHeightMix: 70, // разнообразие цвета
hueSpeed: 20, // град/сек — скорость "перелива" со временем
hueBase: 0, // сдвиг базового оттенка (0-360), можно покрутить под свою палитру
magBoost: 2.1,
baseAlpha: 0.10,
edgeAlphaBoost: 0.34,

// призма
contourFreq: 4.2,
contourSharpness: 7,
contourStrength: 0.55,
spectrumSpan: 300, // ширина радуги в градусах
spectrumFlowSpeed: 36, // град/сек — спектр "течёт" вдоль складки со временем


sweep: {
enabled: true,
angleDeg: 35,
width: 0.16,
speed: 0.065,
strength: 0.20,
chromatic: true,
},

// настройки мобилки
mobile: {
breakpoint: 768,
bufferWidth: 200,
foldOctaves: 3,
updateMs: 100,
opacityMultiplier: 0.3,
blurPx: 2,
},
};

function hash(x, y) {
const n = Math.sin(x * 127.1 + y * 311.7) * 43758.5453123;
return n - Math.floor(n);
}
function smooth(t) { return t * t * (3 - 2 * t); }
function valueNoise(x, y) {
const xi = Math.floor(x), yi = Math.floor(y);
const xf = x - xi, yf = y - yi;
const a = hash(xi, yi), b = hash(xi + 1, yi);
const c = hash(xi, yi + 1), d = hash(xi + 1, yi + 1);
const u = smooth(xf), v = smooth(yf);
return a + (b - a) * u + (c - a) * v + (a - b - c + d) * u * v;
}
function fbm(x, y, octaves) {
let total = 0, amp = 0.5, freq = 1, max = 0;
for (let i = 0; i < octaves; i++) {
total += valueNoise(x * freq, y * freq) * amp;
max += amp; amp *= 0.53; freq *= 2.05;
}
return total / max;
}

function hsl2rgb(h, s, l) {
h = ((h % 360) + 360) % 360;
s /= 100; l /= 100;
const c = (1 - Math.abs(2 * l - 1)) * s;
const x = c * (1 - Math.abs((h / 60) % 2 - 1));
const m = l - c / 2;
let r = 0, g = 0, b = 0;
if (h < 60) { r = c; g = x; b = 0; }
else if (h < 120) { r = x; g = c; b = 0; }
else if (h < 180) { r = 0; g = c; b = x; }
else if (h < 240) { r = 0; g = x; b = c; }
else if (h < 300) { r = x; g = 0; b = c; }
else { r = c; g = 0; b = x; }
return [(r + m) * 255, (g + m) * 255, (b + m) * 255];
}


const canvas = document.getElementById('atm-aurora-canvas');
const ctx = canvas.getContext('2d');
const buffer = document.createElement('canvas');
const bctx = buffer.getContext('2d');
const prefersReduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;

let W = 0, H = 0, bufW = 0, bufH = 0;
let cfg = CONFIG;

function applyResponsive() {
const isMobile = window.innerWidth <= CONFIG.mobile.breakpoint;
cfg = isMobile ? { ...CONFIG, ...CONFIG.mobile } : CONFIG;
canvas.style.filter = `blur(${cfg.blurPx}px)`;
}

function resize() {
applyResponsive();
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W;
canvas.height = H;

bufW = cfg.bufferWidth;
bufH = Math.max(1, Math.round(bufW * (H / W)));
buffer.width = bufW;
buffer.height = bufH;
}


function surfaceHeight(x, y, t, warpOffsetX, warpOffsetY) {
const sx = x + warpOffsetX + t * cfg.driftSpeed * 0.12;
const sy = y + warpOffsetY;
return fbm(sx, sy, cfg.foldOctaves);
}

function renderFrame(t) {
const img = bctx.createImageData(bufW, bufH);
const data = img.data;
const eps = 0.012;
const aspect = bufH / bufW;

for (let py = 0; py < bufH; py++) {
const ny = (py / bufH) * aspect;
for (let px = 0; px < bufW; px++) {
const nx = px / bufW;
const X = nx * cfg.foldScale;
const Y = ny * cfg.foldScale;

const wx = (fbm(X * 0.55 + Math.sin(t * 0.18) * 0.5, Y * 0.55 + Math.cos(t * 0.15) * 0.5, 2) - 0.5) * cfg.warpAmount;
const wy = (fbm(X * 0.55 + Math.cos(t * 0.2) * 0.5, Y * 0.55 + Math.sin(t * 0.17) * 0.5, 2) - 0.5) * cfg.warpAmount;

const hC = surfaceHeight(X, Y, t, wx, wy);
const hX = surfaceHeight(X + eps, Y, t, wx, wy);
const hY = surfaceHeight(X, Y + eps, t, wx, wy);
const gx = (hX - hC) / eps;
const gy = (hY - hC) / eps;

const angle = Math.atan2(gy, gx);
const mag = Math.min(1, Math.sqrt(gx * gx + gy * gy) * cfg.magBoost);

let hue = (angle / (Math.PI * 2)) * 360 + hC * cfg.hueHeightMix + t * cfg.hueSpeed + cfg.hueBase;
const sat = cfg.saturation[0] + (cfg.saturation[1] - cfg.saturation[0]) * hC;
const light = cfg.lightness[0] + (cfg.lightness[1] - cfg.lightness[0]) * mag;

let [r, g, b] = hsl2rgb(hue, sat, light);
let alpha = cfg.baseAlpha + cfg.edgeAlphaBoost * mag;


const phase = hC * cfg.contourFreq;
const frac = phase - Math.floor(phase);
const distToRidge = Math.min(frac, 1 - frac) * 2;
const ridge = Math.pow(1 - distToRidge, cfg.contourSharpness);

if (ridge > 0.003) {
const spectrumHue = frac * cfg.spectrumSpan + t * cfg.spectrumFlowSpeed;
const [rr, rg, rb] = hsl2rgb(spectrumHue, 88, 68);
const mix = ridge * cfg.contourStrength;
r += (rr - r) * mix;
g += (rg - g) * mix;
b += (rb - b) * mix;
alpha = Math.min(1, alpha + ridge * cfg.contourStrength * 0.6);
}


if (cfg.sweep.enabled) {
const sAng = (cfg.sweep.angleDeg * Math.PI) / 180;
const proj = nx * Math.cos(sAng) + (py / bufH) * Math.sin(sAng);
const sweepPos = (t * cfg.sweep.speed) % 1.4;
const dist = Math.abs(proj - sweepPos);
const baseGlow = Math.exp(-((dist / cfg.sweep.width) ** 2)) * cfg.sweep.strength;

if (cfg.sweep.chromatic) {
const spectrumHue2 = ((proj - sweepPos) / cfg.sweep.width) * 140 + t * cfg.spectrumFlowSpeed;
const [sr, sg, sb] = hsl2rgb(spectrumHue2, 80, 72);
r += (sr - r) * baseGlow;
g += (sg - g) * baseGlow;
b += (sb - b) * baseGlow;
alpha = Math.min(1, alpha + baseGlow * 0.5);
} else {
r += (255 - r) * baseGlow;
g += (255 - g) * baseGlow;
b += (255 - b) * baseGlow;
alpha = Math.min(1, alpha + baseGlow * 0.5);
}
}

alpha = Math.min(1, alpha * cfg.opacityMultiplier);

const idx = (py * bufW + px) * 4;
data[idx] = r;
data[idx + 1] = g;
data[idx + 2] = b;
data[idx + 3] = Math.round(alpha * 255);
}
}
bctx.putImageData(img, 0, 0);
}

function draw() {
ctx.clearRect(0, 0, W, H);
ctx.imageSmoothingEnabled = true;
ctx.drawImage(buffer, 0, 0, bufW, bufH, 0, 0, W, H);
}

let lastUpdate = 0;
let tClock = 0;

function loop(now) {
if (now - lastUpdate >= cfg.updateMs) {
tClock += (now - lastUpdate) / 1000;
lastUpdate = now;
renderFrame(tClock);
draw();
}
if (!prefersReduced) requestAnimationFrame(loop);
}

window.addEventListener('resize', resize);
resize();
renderFrame(0);
draw();

if (!prefersReduced) requestAnimationFrame(loop);
})();
</script>
soft glow EFFECT
Мягкое свечение постепенно появляется и растворяется, создавая вокруг объектов лёгкую световую ауру. Помогает сделать тёмный интерфейс более объёмным.
преимущественно для тёмного фона
кликните на картинку для демонстрации
<!-- soft glow by kavaevacreate -->


<div id="atm-dreamy" style="position:fixed; inset:0; pointer-events:none; z-index:2147483641; overflow:hidden;">
<canvas id="atm-dreamy-haze" style="position:absolute; inset:0; width:100%; height:100%; display:block; transform:scale(1.02);"></canvas>
<canvas id="atm-dreamy-dust" style="position:absolute; inset:0; width:100%; height:100%; display:block;"></canvas>
</div>

<script>
(function () {
const CONFIG = {
opacityMultiplier: 1.0,

// дымка
haze: {
bufferWidth: 150,
updateMs: 90,
blurPx: 10, // сильно размыто
scale: 1.15, // крупные пятна
octaves: 3,
warpAmount: 0.6,
driftSpeed: 0.045,
hueBase: 40, // тёплое золото
hueRange: 25,
saturation: [10, 22],
lightness: [58, 92],
baseAlpha: 0.14,
edgeAlphaBoost: 0.20,
},

// ---- мелкая пыль
dust: {
countPerMillionPx: 110,
countMin: 40,
countMax: 220,
size: [0.4, 1.1],
alpha: [0.15, 0.55],
color: [255, 244, 214],
twinkleSpeed: [0.4, 1.2],
driftPx: 7,
driftSpeed: 0.06,
},

// вспышки-звёздочки
flares: {
countPerMillionPx: 9,
countMin: 4,
countMax: 18,
size: [1.2, 2.4],
lengthMult: [9, 16], // во сколько раз луч длиннее размера
color: [255, 250, 235],
flashChancePerSec: 0.12,
flashDecay: 0.9,
holdMs: 'decay', // 0 = обычный decay-затухание
},

mobile: {
breakpoint: 768,
opacityMultiplier: 1.4,
haze: { bufferWidth: 90, blurPx: 7, octaves: 2, updateMs: 110 },
dust: { countPerMillionPx: 75, countMax: 140 },
flares: { countPerMillionPx: 7, countMax: 12 },
},
};

function hash(x, y) {
const n = Math.sin(x * 127.1 + y * 311.7) * 43758.5453123;
return n - Math.floor(n);
}
function smooth(t) { return t * t * (3 - 2 * t); }
function valueNoise(x, y) {
const xi = Math.floor(x), yi = Math.floor(y);
const xf = x - xi, yf = y - yi;
const a = hash(xi, yi), b = hash(xi + 1, yi);
const c = hash(xi, yi + 1), d = hash(xi + 1, yi + 1);
const u = smooth(xf), v = smooth(yf);
return a + (b - a) * u + (c - a) * v + (a - b - c + d) * u * v;
}
function fbm(x, y, octaves) {
let total = 0, amp = 0.5, freq = 1, max = 0;
for (let i = 0; i < octaves; i++) {
total += valueNoise(x * freq, y * freq) * amp;
max += amp; amp *= 0.55; freq *= 2.0;
}
return total / max;
}
function hsl2rgb(h, s, l) {
h = ((h % 360) + 360) % 360;
s /= 100; l /= 100;
const c = (1 - Math.abs(2 * l - 1)) * s;
const x = c * (1 - Math.abs((h / 60) % 2 - 1));
const m = l - c / 2;
let r = 0, g = 0, b = 0;
if (h < 60) { r = c; g = x; b = 0; }
else if (h < 120) { r = x; g = c; b = 0; }
else if (h < 180) { r = 0; g = c; b = x; }
else if (h < 240) { r = 0; g = x; b = c; }
else if (h < 300) { r = x; g = 0; b = c; }
else { r = c; g = 0; b = x; }
return [(r + m) * 255, (g + m) * 255, (b + m) * 255];
}
function rand(min, max) { return min + Math.random() * (max - min); }


const hazeCanvas = document.getElementById('atm-dreamy-haze');
const hctx = hazeCanvas.getContext('2d');
const hazeBuf = document.createElement('canvas');
const hbctx = hazeBuf.getContext('2d');

const dustCanvas = document.getElementById('atm-dreamy-dust');
const dctx = dustCanvas.getContext('2d');

const prefersReduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;

let W = 0, H = 0, bufW = 0, bufH = 0;
let cfg = CONFIG;
let dust = [];
let flares = [];

function mergeCfg() {
const isMobile = window.innerWidth <= CONFIG.mobile.breakpoint;
if (!isMobile) return CONFIG;
return {
...CONFIG,
opacityMultiplier: CONFIG.mobile.opacityMultiplier,
haze: { ...CONFIG.haze, ...CONFIG.mobile.haze },
dust: { ...CONFIG.dust, ...CONFIG.mobile.dust },
flares: { ...CONFIG.flares, ...CONFIG.mobile.flares },
};
}

function makeDust() {
return {
x: rand(0, W), y: rand(0, H),
size: rand(cfg.dust.size[0], cfg.dust.size[1]),
baseAlpha: rand(cfg.dust.alpha[0], cfg.dust.alpha[1]),
phase: rand(0, Math.PI * 2),
twinkleSpeed: rand(cfg.dust.twinkleSpeed[0], cfg.dust.twinkleSpeed[1]),
jx: rand(-1, 1), jy: rand(-1, 1),
};
}

function makeFlare() {
return {
x: rand(0, W), y: rand(0, H),
size: rand(cfg.flares.size[0], cfg.flares.size[1]),
lenMult: rand(cfg.flares.lengthMult[0], cfg.flares.lengthMult[1]),
flash: rand(0, 0.3),
nextCheck: 0,
};
}

function targetCount(density, min, max) {
const n = Math.round((W * H) / 1000000 * density);
return Math.max(min, Math.min(max, n));
}

function resize() {
cfg = mergeCfg();
W = window.innerWidth;
H = window.innerHeight;

hazeCanvas.width = W; hazeCanvas.height = H;
dustCanvas.width = W; dustCanvas.height = H;
hazeCanvas.style.filter = `blur(${cfg.haze.blurPx}px)`;

bufW = cfg.haze.bufferWidth;
bufH = Math.max(1, Math.round(bufW * (H / W)));
hazeBuf.width = bufW; hazeBuf.height = bufH;

const dustTarget = targetCount(cfg.dust.countPerMillionPx, cfg.dust.countMin, cfg.dust.countMax);
if (dust.length < dustTarget) { for (let i = 0; i < dustTarget - dust.length; i++) dust.push(makeDust()); }
else if (dust.length > dustTarget) { dust.length = dustTarget; }

const flareTarget = targetCount(cfg.flares.countPerMillionPx, cfg.flares.countMin, cfg.flares.countMax);
if (flares.length < flareTarget) { for (let i = 0; i < flareTarget - flares.length; i++) flares.push(makeFlare()); }
else if (flares.length > flareTarget) { flares.length = flareTarget; }
}


function renderHaze(t) {
const img = hbctx.createImageData(bufW, bufH);
const data = img.data;
const aspect = bufH / bufW;
const h = cfg.haze;

for (let py = 0; py < bufH; py++) {
const ny = (py / bufH) * aspect;
for (let px = 0; px < bufW; px++) {
const nx = px / bufW;
const X = nx * h.scale + Math.sin(t * h.driftSpeed) * 0.4;
const Y = ny * h.scale + Math.cos(t * h.driftSpeed * 0.8) * 0.4;

const wob = fbm(X * 0.6 + t * h.driftSpeed * 0.3, Y * 0.6, 2);
const val = fbm(X + (wob - 0.5) * h.warpAmount, Y + (wob - 0.5) * h.warpAmount, h.octaves);

const hue = h.hueBase + Math.sin(val * 4 + t * 0.2) * h.hueRange;
const sat = h.saturation[0] + (h.saturation[1] - h.saturation[0]) * val;
const light = h.lightness[0] + (h.lightness[1] - h.lightness[0]) * val;

const [r, g, b] = hsl2rgb(hue, sat, light);
let alpha = (h.baseAlpha + h.edgeAlphaBoost * val) * cfg.opacityMultiplier;

const idx = (py * bufW + px) * 4;
data[idx] = r; data[idx + 1] = g; data[idx + 2] = b;
data[idx + 3] = Math.round(Math.min(1, alpha) * 255);
}
}
hbctx.putImageData(img, 0, 0);
}

function drawHaze() {
hctx.clearRect(0, 0, W, H);
hctx.imageSmoothingEnabled = true;
hctx.drawImage(hazeBuf, 0, 0, bufW, bufH, 0, 0, W, H);
}

function drawDust(now) {
const t = now / 1000;
const [rC, gC, bC] = cfg.dust.color;
dctx.save();
for (const p of dust) {
const jx = Math.sin(t * cfg.dust.driftSpeed * 6 + p.phase) * cfg.dust.driftPx * p.jx;
const jy = Math.cos(t * cfg.dust.driftSpeed * 5 + p.phase) * cfg.dust.driftPx * p.jy;
const twinkle = 0.5 + 0.5 * Math.sin(t * p.twinkleSpeed + p.phase);
const alpha = p.baseAlpha * (0.35 + 0.65 * twinkle) * Math.min(1, cfg.opacityMultiplier);
dctx.fillStyle = `rgba(${rC},${gC},${bC},${alpha})`;
dctx.beginPath();
dctx.arc(p.x + jx, p.y + jy, p.size, 0, Math.PI * 2);
dctx.fill();
}
dctx.restore();
}


function updateFlares(dt, now) {
for (const f of flares) {
f.flash *= Math.pow(cfg.flares.flashDecay, dt * 60);
if (now >= f.nextCheck) {
f.nextCheck = now + rand(600, 1800);
if (Math.random() < cfg.flares.flashChancePerSec * 1.2) f.flash = 1;
}
}
}

function drawFlares() {
const [rC, gC, bC] = cfg.flares.color;
for (const f of flares) {
if (f.flash < 0.02) continue;
const len = f.size * f.lenMult * f.flash;
const core = f.size * 1.1 * f.flash;

dctx.save();
dctx.globalAlpha = f.flash;

dctx.fillStyle = `rgba(${rC},${gC},${bC},1)`;
dctx.beginPath();
dctx.arc(f.x, f.y, core, 0, Math.PI * 2);
dctx.fill();

const dirs = [[1, 0], [-1, 0], [0, 1], [0, -1]];
for (const [dx, dy] of dirs) {
const grad = dctx.createLinearGradient(f.x, f.y, f.x + dx * len, f.y + dy * len);
grad.addColorStop(0, `rgba(${rC},${gC},${bC},0.9)`);
grad.addColorStop(1, `rgba(${rC},${gC},${bC},0)`);
dctx.strokeStyle = grad;
dctx.lineWidth = Math.max(0.4, f.size * 0.3);
dctx.beginPath();
dctx.moveTo(f.x, f.y);
dctx.lineTo(f.x + dx * len, f.y + dy * len);
dctx.stroke();
}
dctx.restore();
}
}

function drawDustLayer(now) {
dctx.clearRect(0, 0, W, H);
drawDust(now);
drawFlares();
}

let lastHazeUpdate = 0;
let lastFrame = performance.now();
let tClock = 0;

function loop(now) {
const dt = Math.min(0.05, (now - lastFrame) / 1000);
lastFrame = now;
tClock += dt;

updateFlares(dt, now);
drawDustLayer(now);

if (now - lastHazeUpdate >= cfg.haze.updateMs) {
renderHaze(tClock);
drawHaze();
lastHazeUpdate = now;
}

if (!prefersReduced) requestAnimationFrame(loop);
}

window.addEventListener('resize', resize);
resize();
renderHaze(0);
drawHaze();
drawDustLayer(performance.now());

if (!prefersReduced) requestAnimationFrame(loop);
})();
</script>


Html code will be here

bloom gradient
Эффект яркого переливающегося фона. При создании сайта удалите фон у блоков. Подходит и для стандартных и для zero. Плашки под текст делать можно.
готовый фон
кликните на картинку для демонстрации
<!-- bloom gradient by kavaevacreate -->


<div id="atm-bloom" style="position:fixed; inset:0; pointer-events:none; z-index:0; overflow:hidden;">
<canvas id="atm-bloom-canvas" style="position:absolute; inset:0; width:100%; height:100%; display:block; transform:scale(1.03);"></canvas>
</div>

<script>
(function () {
const CONFIG = {
bufferWidth: 150,
updateMs: 90,
blurPx: 22, // мягкое рассеивание

loopSeconds: 32, // длительность одного полного бесшовного цикла

scale: 2.05, // крупные облака, не мелкая рябь
octaves: 4,
warpAmount: 0.95,

// ---- палитра
hueBase: 300, // стартовый оттенок (розово-сиреневый), крути под свою палитру
hueSpatialRange: 190,
hueDriftRange: 55,
saturation: [45, 72], // насыщенность
lightness: [62, 92],
softness: 1.35,


// мобилка
mobile: {
breakpoint: 768,
bufferWidth: 100,
octaves: 3,
updateMs: 120,
blurPx: 16,
},
};

function hash(x, y) {
const n = Math.sin(x * 127.1 + y * 311.7) * 43758.5453123;
return n - Math.floor(n);
}
function smooth(t) { return t * t * (3 - 2 * t); }
function valueNoise(x, y) {
const xi = Math.floor(x), yi = Math.floor(y);
const xf = x - xi, yf = y - yi;
const a = hash(xi, yi), b = hash(xi + 1, yi);
const c = hash(xi, yi + 1), d = hash(xi + 1, yi + 1);
const u = smooth(xf), v = smooth(yf);
return a + (b - a) * u + (c - a) * v + (a - b - c + d) * u * v;
}
function fbm(x, y, octaves) {
let total = 0, amp = 0.5, freq = 1, max = 0;
for (let i = 0; i < octaves; i++) {
total += valueNoise(x * freq, y * freq) * amp;
max += amp; amp *= 0.55; freq *= 2.0;
}
return total / max;
}
function hsl2rgb(h, s, l) {
h = ((h % 360) + 360) % 360;
s /= 100; l /= 100;
const c = (1 - Math.abs(2 * l - 1)) * s;
const x = c * (1 - Math.abs((h / 60) % 2 - 1));
const m = l - c / 2;
let r = 0, g = 0, b = 0;
if (h < 60) { r = c; g = x; b = 0; }
else if (h < 120) { r = x; g = c; b = 0; }
else if (h < 180) { r = 0; g = c; b = x; }
else if (h < 240) { r = 0; g = x; b = c; }
else if (h < 300) { r = x; g = 0; b = c; }
else { r = c; g = 0; b = x; }
return [(r + m) * 255, (g + m) * 255, (b + m) * 255];
}

const canvas = document.getElementById('atm-bloom-canvas');
const ctx = canvas.getContext('2d');
const buffer = document.createElement('canvas');
const bctx = buffer.getContext('2d');
const prefersReduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;

let W = 0, H = 0, bufW = 0, bufH = 0;
let cfg = CONFIG;

function applyResponsive() {
const isMobile = window.innerWidth <= CONFIG.mobile.breakpoint;
cfg = isMobile ? { ...CONFIG, ...CONFIG.mobile } : CONFIG;
canvas.style.filter = `blur(${cfg.blurPx}px)`;
}

function resize() {
applyResponsive();
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W;
canvas.height = H;

bufW = cfg.bufferWidth;
bufH = Math.max(1, Math.round(bufW * (H / W)));
buffer.width = bufW;
buffer.height = bufH;
}

function cloudValue(x, y, phase) {
const w1 = Math.sin(phase) * 0.55;
const w2 = Math.cos(phase * 0.82) * 0.55;

const warpX = fbm(x * 0.55 + w1, y * 0.55 + w2, 2);
const warpY = fbm(x * 0.55 + w2, y * 0.55 - w1, 2);

const driftX = Math.sin(phase * 0.5) * 0.35;
const driftY = Math.cos(phase * 0.47) * 0.35;

const sx = x + (warpX - 0.5) * cfg.warpAmount + driftX;
const sy = y + (warpY - 0.5) * cfg.warpAmount + driftY;

return fbm(sx, sy, cfg.octaves);
}

function renderFrame(phase) {
const img = bctx.createImageData(bufW, bufH);
const data = img.data;
const aspect = bufH / bufW;

for (let py = 0; py < bufH; py++) {
const ny = (py / bufH) * aspect;
for (let px = 0; px < bufW; px++) {
const nx = px / bufW;
const X = nx * cfg.scale;
const Y = ny * cfg.scale;

const val = cloudValue(X, Y, phase);

const hue = cfg.hueBase + (val - 0.5) * 2 * cfg.hueSpatialRange + Math.sin(phase * 0.6) * cfg.hueDriftRange;
const sat = cfg.saturation[0] + (cfg.saturation[1] - cfg.saturation[0]) * val;
const light = cfg.lightness[0] + (cfg.lightness[1] - cfg.lightness[0]) * val;

const [r, g, b] = hsl2rgb(hue, sat, light);

const idx = (py * bufW + px) * 4;
data[idx] = r; data[idx + 1] = g; data[idx + 2] = b;
data[idx + 3] = 255;
}
}
bctx.putImageData(img, 0, 0);
}

function draw() {
ctx.clearRect(0, 0, W, H);
ctx.imageSmoothingEnabled = true;
ctx.drawImage(buffer, 0, 0, bufW, bufH, 0, 0, W, H);
}

let lastUpdate = 0;
let startTime = performance.now();

function loop(now) {
if (now - lastUpdate >= cfg.updateMs) {
lastUpdate = now;
const elapsed = ((now - startTime) / 1000) % cfg.loopSeconds;
const phase = (elapsed / cfg.loopSeconds) * Math.PI * 2;
renderFrame(phase);
draw();
}
if (!prefersReduced) requestAnimationFrame(loop);
}

window.addEventListener('resize', resize);
resize();
renderFrame(0);
draw();

if (!prefersReduced) requestAnimationFrame(loop);
})();
</script>

sky drift
Облака и солнечные просветы медленно движутся по экрану, создавая ощущение настоящего неба. При создании сайта удалите фон у блоков. Подходит и для стандартных и для zero. Плашки под текст делать можно.
готовый фон
кликните на картинку для демонстрации
<!-- sky drift by kavaevacreate -->


<div id="atm-sky" style="position:fixed; inset:0; pointer-events:none; z-index:-1; overflow:hidden;">
<canvas id="atm-sky-canvas" style="position:absolute; inset:0; width:100%; height:100%; display:block; transform:scale(1.02);"></canvas>
</div>

<script>
(function () {
const CONFIG = {
// ПАЛИТРА
skyColorTop: [120, 165, 215], // цвет неба вверху
skyColorBottom: [200, 220, 240], // цвет неба у горизонта/облаков
cloudColor: [255, 255, 255], // цвет самих облаков
rayColor: [255, 248, 220], // цвет лучей солнца

// солнце
sun: {
x: 0.5, y: 0.12, // позиция солнца, доли от ширины/высоты экрана
glowRadius: 0.35,
glowStrength: 0.8,
},

// лучи
rays: {
count: 14,
sharpness: 2.2,
falloff: 0.55,
opacity: 0.55,
speed: 0.03, // скорость вращения лучей
},

// облака
cloud: {
scale: 2.0,
octaves: 4,
warpAmount: 0.7,
thresholdLow: 0.30, // ниже — чистое небо
thresholdHigh: 0.62, // выше — плотное облако
softness: 1.0, // мягкость края облака
},

loopSeconds: 60, // длительность полного бесшовного цикла

bufferWidth: 190,
updateMs: 90,
blurPx: 2.5,
opacityMultiplier: 1.0,


// настройки мобилки
mobile: {
breakpoint: 768,
bufferWidth: 140,
cloud: { octaves: 2 },
updateMs: 110,
blurPx: 3.5,
},
};

function hash(x, y) {
const n = Math.sin(x * 127.1 + y * 311.7) * 43758.5453123;
return n - Math.floor(n);
}
function smooth(t) { return t * t * (3 - 2 * t); }
function valueNoise(x, y) {
const xi = Math.floor(x), yi = Math.floor(y);
const xf = x - xi, yf = y - yi;
const a = hash(xi, yi), b = hash(xi + 1, yi);
const c = hash(xi, yi + 1), d = hash(xi + 1, yi + 1);
const u = smooth(xf), v = smooth(yf);
return a + (b - a) * u + (c - a) * v + (a - b - c + d) * u * v;
}
function fbm(x, y, octaves) {
let total = 0, amp = 0.5, freq = 1, max = 0;
for (let i = 0; i < octaves; i++) {
total += valueNoise(x * freq, y * freq) * amp;
max += amp; amp *= 0.53; freq *= 2.05;
}
return total / max;
}
function billow(x, y, octaves) {
let total = 0, amp = 0.5, freq = 1, max = 0;
for (let i = 0; i < octaves; i++) {
const n = valueNoise(x * freq, y * freq);
total += (1 - Math.abs(n * 2 - 1)) * amp;
max += amp; amp *= 0.53; freq *= 2.05;
}
return total / max;
}
function clamp01(v) { return Math.max(0, Math.min(1, v)); }
function smoothstep(e0, e1, x) {
const t = clamp01((x - e0) / (e1 - e0));
return t * t * (3 - 2 * t);
}
function mix(a, b, t) { return a + (b - a) * t; }

const canvas = document.getElementById('atm-sky-canvas');
const ctx = canvas.getContext('2d');
const buffer = document.createElement('canvas');
const bctx = buffer.getContext('2d');
const root = document.getElementById('atm-sky');
const prefersReduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;

let W = 0, H = 0, bufW = 0, bufH = 0;
let cfg = CONFIG;

function applyResponsive() {
const isMobile = window.innerWidth <= CONFIG.mobile.breakpoint;
cfg = isMobile
? { ...CONFIG, ...CONFIG.mobile, cloud: { ...CONFIG.cloud, ...CONFIG.mobile.cloud } }
: CONFIG;
canvas.style.filter = `blur(${cfg.blurPx}px)`;
}

function resize() {
applyResponsive();
const rect = root.getBoundingClientRect();
W = Math.max(1, rect.width);
H = Math.max(1, rect.height);
canvas.width = W;
canvas.height = H;

bufW = cfg.bufferWidth;
bufH = Math.max(1, Math.round(bufW * (H / W)));
buffer.width = bufW;
buffer.height = bufH;
}

function cloudDensity(x, y, phase) {
const w1 = Math.sin(phase) * 0.5;
const w2 = Math.cos(phase * 0.83) * 0.5;
const warpX = fbm(x * 0.5 + w1, y * 0.5 + w2, 2);
const warpY = fbm(x * 0.5 + w2, y * 0.5 - w1, 2);

const driftX = Math.sin(phase * 0.4) * 0.3;
const sx = x + (warpX - 0.5) * cfg.cloud.warpAmount + driftX;
const sy = y + (warpY - 0.5) * cfg.cloud.warpAmount;

return billow(sx, sy, cfg.cloud.octaves);
}

function renderFrame(phase) {
const img = bctx.createImageData(bufW, bufH);
const data = img.data;
const aspect = bufH / bufW;
const [skyTR, skyTG, skyTB] = cfg.skyColorTop;
const [skyBR, skyBG, skyBB] = cfg.skyColorBottom;
const [cldR, cldG, cldB] = cfg.cloudColor;
const [rayR, rayG, rayB] = cfg.rayColor;
const sunPxX = cfg.sun.x;
const sunPxY = cfg.sun.y * aspect;

for (let py = 0; py < bufH; py++) {
const ny = (py / bufH);
const nyAdj = ny * aspect;
for (let px = 0; px < bufW; px++) {
const nx = px / bufW;
const X = nx * cfg.cloud.scale;
const Y = nyAdj * cfg.cloud.scale;

let r = mix(skyTR, skyBR, ny);
let g = mix(skyTG, skyBG, ny);
let b = mix(skyTB, skyBB, ny);

const dx = nx - sunPxX;
const dy = nyAdj - sunPxY;
const dist = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx);
const angleNoise = fbm(angle * 2.2 + phase * 0.15, dist * 2.5, 2);
const spokes = Math.pow(Math.abs(Math.sin(angle * cfg.rays.count / 2 + angleNoise * 1.8 + phase * cfg.rays.speed)), cfg.rays.sharpness);
const rayFalloff = Math.exp(-dist * cfg.rays.falloff * 2);
const rayStrength = spokes * rayFalloff * cfg.rays.opacity;

r = mix(r, rayR, rayStrength);
g = mix(g, rayG, rayStrength);
b = mix(b, rayB, rayStrength);

const density = cloudDensity(X, Y, phase);
const cloudAlpha = smoothstep(cfg.cloud.thresholdLow, cfg.cloud.thresholdHigh, density);

const shade = 0.72 + 0.28 * smoothstep(cfg.cloud.thresholdLow, 1, density);
const finalCloudR = cldR * shade;
const finalCloudG = cldG * shade;
const finalCloudB = cldB * shade;

r = mix(r, finalCloudR, cloudAlpha);
g = mix(g, finalCloudG, cloudAlpha);
b = mix(b, finalCloudB, cloudAlpha);

const glowDist = dist / cfg.sun.glowRadius;
const glow = Math.exp(-glowDist * glowDist) * cfg.sun.glowStrength;
r = mix(r, 255, glow);
g = mix(g, 250, glow);
b = mix(b, 235, glow);

const idx = (py * bufW + px) * 4;
data[idx] = clamp01(r / 255) * 255;
data[idx + 1] = clamp01(g / 255) * 255;
data[idx + 2] = clamp01(b / 255) * 255;
data[idx + 3] = 255;
}
}
bctx.putImageData(img, 0, 0);
}

function draw() {
ctx.clearRect(0, 0, W, H);
ctx.imageSmoothingEnabled = true;
ctx.globalAlpha = cfg.opacityMultiplier;
ctx.drawImage(buffer, 0, 0, bufW, bufH, 0, 0, W, H);
ctx.globalAlpha = 1;
}

let lastUpdate = 0;
let startTime = performance.now();

function loop(now) {
if (now - lastUpdate >= cfg.updateMs) {
lastUpdate = now;
const elapsed = ((now - startTime) / 1000) % cfg.loopSeconds;
const phase = (elapsed / cfg.loopSeconds) * Math.PI * 2;
renderFrame(phase);
draw();
}
if (!prefersReduced) requestAnimationFrame(loop);
}

window.addEventListener('resize', resize);
resize();
renderFrame(0);
draw();

if (!prefersReduced) requestAnimationFrame(loop);
})();
</script>

Made on
Tilda