Aurora Lab I - the footer bloom. A gradient that rises to meet the pointer. Provenance first: Bruno, a French branding agency full of talented people who have shaped identities for famous companies, closed one of their projects with an incredible gradient at the foot of the page. This lab is my attempt to replicate that effect. Everything is on sliders: ink, shape, motion, response, film grain. When it feels right, take it with you: COPY CODE carries a drop-in snippet with your values baked in, DOWNLOAD HTML a standalone file, SAVE PNG a still. The recipe string below holds the exact state - copy it to save or share a setting, paste one in and APPLY it. I'm still not sure the bloom belongs on my own pages, but I love the effect too much not to file it. Presets are starting points; OLD GLORY is the boot state.

FBI

Flo Bureau of Investigation
MOVE THE POINTER OVER THE FOOTER
${buildSnippet()} `; } document.getElementById('codeBtn').onclick = (e) => { navigator.clipboard.writeText(buildSnippet()); flash(e.target, 'COPIED'); }; document.getElementById('htmlBtn').onclick = (e) => { const a = document.createElement('a'); a.download = 'aurora-bloom.html'; a.href = URL.createObjectURL(new Blob([buildHTML()], { type: 'text/html' })); a.click(); URL.revokeObjectURL(a.href); flash(e.target, 'SAVED'); }; /* --- sizing --- */ let cardW = 800, cardH = 520; function sizeStage() { card.style.height = P.stageH + 'px'; cardW = card.clientWidth; cardH = card.clientHeight; sizeBuffer(); applyBlur(); } function sizeBuffer() { buf.width = P.res; buf.height = Math.max(32, Math.round(P.res * cardH / cardW)); } function applyBlur() { cv.style.filter = P.blur ? `blur(${P.blur}px)` : ''; const s = 1 + (P.blur * 2.5) / cardW; cv.style.transform = `scale(${s.toFixed(3)})`; } new ResizeObserver(() => { cardW = card.clientWidth; cardH = card.clientHeight; sizeBuffer(); applyBlur(); }) .observe(card); /* --- pointer state --- */ let pointerIn = false, nx = 0.5, ny = 0.5; let amp = 0, crestX = P.homeX; card.addEventListener('pointermove', e => { const r = card.getBoundingClientRect(); nx = clamp((e.clientX - r.left) / r.width, 0, 1); ny = clamp((e.clientY - r.top) / r.height, 0, 1); pointerIn = true; }); card.addEventListener('pointerleave', () => { pointerIn = false; }); /* --- painting --- */ function ramp(g) { const s = P.ramp; g.addColorStop(0, P.core); g.addColorStop(0.38 * s, P.core); g.addColorStop(0.58 * s, P.mid); g.addColorStop(0.76 * s, P.band); g.addColorStop(Math.min(0.92 * s, 0.97), hexA(P.halo, 0.9)); g.addColorStop(1, hexA(P.halo, 0)); } function blob(ctx, W, H, cxN, r) { if (r <= 0) return; ctx.save(); ctx.translate(cxN * W, H + r * P.sink); ctx.scale(P.crestW, 1); const g = ctx.createRadialGradient(0, 0, 0, 0, 0, r); ramp(g); ctx.fillStyle = g; ctx.fillRect(-r, -r, r * 2, r * 2); ctx.restore(); } function paint(ctx, W, H, a, cX) { ctx.fillStyle = P.bg; ctx.fillRect(0, 0, W, H); const r = (a / 100) * H; if (P.side > 0) { blob(ctx, W, H, cX - 0.38, r * 0.62 * P.side); blob(ctx, W, H, cX + 0.38, r * 0.45 * P.side); } blob(ctx, W, H, cX, r); } function drawFrame(t) { const targetAmp = pointerIn ? clamp((ny - (1 - P.range)) / P.range, 0, 1) : 0; const e = reduced ? 1 : (targetAmp > amp ? P.riseEase : P.fallEase); amp += (targetAmp - amp) * e; const targetX = pointerIn ? P.homeX + (nx - P.homeX) * P.followX : P.homeX; crestX += (targetX - crestX) * (reduced ? 1 : P.riseEase * 1.2); const breathe = reduced ? 0 : Math.sin(t * 0.0006 * P.breatheSpd) * P.breathe * 6; const a = P.base + (P.bloom - P.base) * amp + breathe; paint(bctx, buf.width, buf.height, a, crestX); if (cv.width !== buf.width || cv.height !== buf.height) { cv.width = buf.width; cv.height = buf.height; } cx2d.clearRect(0, 0, cv.width, cv.height); cx2d.drawImage(buf, 0, 0); specline.textContent = `AMP ${amp.toFixed(2)} · CREST X ${crestX.toFixed(2)} · BUFFER ${buf.width}×${buf.height} · BLUR ${P.blur}PX`; } /* --- png export: the gradient alone, at current state --- */ document.getElementById('pngBtn').onclick = () => { const W = 1600, H = Math.round(1600 * cardH / cardW); const out = document.createElement('canvas'); out.width = W; out.height = H; const octx = out.getContext('2d'); const breathe = reduced ? 0 : Math.sin(performance.now() * 0.0006 * P.breatheSpd) * P.breathe * 6; const a = P.base + (P.bloom - P.base) * amp + breathe; paint(bctx, buf.width, buf.height, a, crestX); octx.fillStyle = P.bg; octx.fillRect(0, 0, W, H); octx.filter = `blur(${(P.blur * W / cardW).toFixed(1)}px)`; const s = 1 + (P.blur * 2.5) / cardW; octx.drawImage(buf, (W - W * s) / 2, (H - H * s) / 2, W * s, H * s); const link = document.createElement('a'); link.download = 'fbi-aurora.png'; link.href = out.toDataURL('image/png'); link.click(); }; /* --- boot --- */ grainEl.style.opacity = P.grain; syncPanel(); writeRecipe(); sizeStage(); if (reduced) { pointerIn = true; ny = 0.75; nx = 0.5; drawFrame(0); } else { (function loop(t) { drawFrame(t); requestAnimationFrame(loop); })(0); }