Logo Heatmap New
A wordmark glowing like a thermal image: heat rises from the letters and flares around the cursor. For a striking brand intro or campaign page.
Brouwerij Kade · Zwolle
How to use it
- Paste the HTML where the backgrounds & webgl should appear.
- Add the CSS to your stylesheet. All classes are prefixed, so nothing clashes with your own styles.
- Add the JavaScript and call init() with the element that contains the HTML.
<!-- Logo Heatmap · Bron: Paper Shaders · licentie Apache-2.0. Laat deze vermelding staan bij hergebruik. -->
<div class="jouw-container">
<div class="lhm"><div class="lhm__fb"></div><canvas class="lhm__gl" aria-hidden="true"></canvas><span class="lhm__tag">Brouwerij Kade · Zwolle</span></div>
</div>/* Logo Heatmap · Bron: Paper Shaders · licentie Apache-2.0. Laat deze vermelding staan bij hergebruik. */
.lhm{position:absolute;inset:0;overflow:hidden;background:#f4f3f1;font-family:'Switzer',system-ui,sans-serif}
.lhm__gl,.lhm__fb,.lhm__fb canvas{position:absolute;inset:0;width:100%;height:100%;display:block}
.lhm.is-nogl .lhm__gl{display:none}
.lhm__tag{position:absolute;left:12px;bottom:12px;z-index:3;font:500 10px/1 'JetBrains Mono',monospace;letter-spacing:.14em;text-transform:uppercase;background:#0a0a0a;color:#fff;padding:6px 8px;border-radius:3px}
.lhm__ctl{position:absolute;right:12px;bottom:12px;z-index:3;display:flex;align-items:center;gap:8px;background:#fff;border:1px solid rgba(10,10,10,.14);border-radius:4px;padding:6px 9px;font:500 10px/1 'JetBrains Mono',monospace;letter-spacing:.1em;text-transform:uppercase;color:#0a0a0a}
.lhm__ctl input{width:84px;accent-color:#F5480F;margin:0}
.lhm.is-nogl .lhm__ctl{display:none}
.lhm{background:#0a0a0a}
.lhm__fb{background:radial-gradient(ellipse 60% 55% at 50% 52%,rgba(245,72,15,.75),rgba(120,30,5,.35) 45%,#0a0a0a 75%)}
.lhm__fb canvas{mix-blend-mode:screen;opacity:.92}
.lhm__fb::after{content:'';position:absolute;inset:0;background:radial-gradient(ellipse 40% 35% at 50% 50%,rgba(255,190,120,.35),transparent 70%);animation:lhm-pulse 2.8s ease-in-out infinite}
@keyframes lhm-pulse{50%{opacity:.35}}
@media(prefers-reduced-motion:reduce){.lhm__fb::after{animation:none}}// Logo Heatmap · Bron: Paper Shaders · licentie Apache-2.0. Laat deze vermelding staan bij hergebruik.
// root = het element waarin de HTML staat
function init(root){
function mkGL(cv, frag) {
var gl = null;
try { gl = cv.getContext('webgl', { antialias: false, premultipliedAlpha: false }) || cv.getContext('experimental-webgl'); } catch (e) { gl = null; }
if (!gl) return null;
function sh(t, s) { var o = gl.createShader(t); gl.shaderSource(o, s); gl.compileShader(o); if (!gl.getShaderParameter(o, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(o) || 'shader'); return o; }
var pr = gl.createProgram();
try {
gl.attachShader(pr, sh(gl.VERTEX_SHADER, 'attribute vec2 p;varying vec2 vUv;void main(){vUv=p*.5+.5;gl_Position=vec4(p,0.,1.);}'));
gl.attachShader(pr, sh(gl.FRAGMENT_SHADER, 'precision highp float;varying vec2 vUv;' + frag));
gl.linkProgram(pr);
if (!gl.getProgramParameter(pr, gl.LINK_STATUS)) throw new Error('link');
} catch (e) { console.warn('lab webgl', e); return null; }
gl.useProgram(pr);
var buf = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
var loc = gl.getAttribLocation(pr, 'p'); gl.enableVertexAttribArray(loc); gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
var U = {};
var api = {
gl: gl,
u: function (n) { if (!(n in U)) U[n] = gl.getUniformLocation(pr, n); return U[n]; },
upd: function (t, src, unit) {
gl.activeTexture(gl.TEXTURE0 + unit); gl.bindTexture(gl.TEXTURE_2D, t);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, src);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
},
tex: function (src, unit, name) { var t = gl.createTexture(); api.upd(t, src, unit); gl.uniform1i(api.u(name), unit); return t; },
fit: function () {
var d = Math.min(window.devicePixelRatio || 1, 2), w = Math.max(1, Math.round(cv.clientWidth * d)), h = Math.max(1, Math.round(cv.clientHeight * d));
if (cv.width !== w || cv.height !== h) { cv.width = w; cv.height = h; }
gl.viewport(0, 0, w, h); gl.uniform2f(api.u('uRes'), w, h); return d;
},
draw: function () { gl.drawArrays(gl.TRIANGLES, 0, 3); }
};
return api;
}
function runLoop(el, frame, still) {
var reduce = matchMedia('(prefers-reduced-motion:reduce)').matches, vis = true, raf = 0, t0 = performance.now();
function tick(now) { raf = 0; frame((now - t0) / 1000); if (!reduce && !still && vis) raf = requestAnimationFrame(tick); }
if ('IntersectionObserver' in window) new IntersectionObserver(function (es) { vis = es[0].isIntersecting; if (vis && !raf) raf = requestAnimationFrame(tick); }).observe(el);
raf = requestAnimationFrame(tick);
return { reduce: reduce, kick: function () { if (!raf) raf = requestAnimationFrame(tick); } };
}
function paintScene(c, v) {
var x = c.getContext('2d'), W = c.width, H = c.height, night = v === 1;
var bg = x.createLinearGradient(0, 0, 0, H);
if (night) { bg.addColorStop(0, '#0a0a0a'); bg.addColorStop(1, '#2b1b12'); } else { bg.addColorStop(0, '#f4f3f1'); bg.addColorStop(1, '#ddd5c9'); }
x.fillStyle = bg; x.fillRect(0, 0, W, H);
var sx = W * .68, sy = H * .44, sr = H * .27;
var sg = x.createRadialGradient(sx - sr * .35, sy - sr * .35, sr * .1, sx, sy, sr);
if (night) { sg.addColorStop(0, '#ffffff'); sg.addColorStop(1, '#cfc8bd'); } else { sg.addColorStop(0, '#ffa071'); sg.addColorStop(1, '#F5480F'); }
x.fillStyle = sg; x.beginPath(); x.arc(sx, sy, sr, 0, 7); x.fill();
x.fillStyle = night ? '#7a2a0c' : '#6b645d';
x.beginPath(); x.moveTo(0, H * .72); x.bezierCurveTo(W * .25, H * .58, W * .45, H * .78, W * .7, H * .66); x.bezierCurveTo(W * .85, H * .6, W * .95, H * .66, W, H * .64); x.lineTo(W, H); x.lineTo(0, H); x.fill();
x.fillStyle = night ? '#F5480F' : '#0a0a0a';
x.beginPath(); x.moveTo(0, H * .84); x.bezierCurveTo(W * .3, H * .74, W * .6, H * .92, W, H * .8); x.lineTo(W, H); x.lineTo(0, H); x.fill();
x.strokeStyle = night ? '#f4f3f1' : '#0a0a0a'; x.lineWidth = H * .012;
x.beginPath(); x.moveTo(W * .2, H * .8); x.lineTo(W * .2, H * .34); x.lineTo(W * .44, H * .34); x.moveTo(W * .16, H * .34); x.lineTo(W * .2, H * .34); x.moveTo(W * .4, H * .34); x.lineTo(W * .4, H * .5); x.stroke();
x.fillStyle = night ? '#f4f3f1' : '#0a0a0a';
x.fillRect(W * .385, H * .5, W * .03, H * .05);
x.textBaseline = 'top';
x.font = '400 ' + Math.round(H * .08) + "px Boldonse, Switzer, sans-serif"; x.fillText('HAVENKADE', W * .06, H * .08);
x.font = '500 ' + Math.round(H * .034) + "px 'JetBrains Mono', monospace"; x.fillText(night ? 'NACHTVERSIE · ROTTERDAM' : 'DAGVERSIE · ROTTERDAM', W * .06, H * .22);
}
function fontsThen(cb) {
if (!document.fonts || !document.fonts.load) return;
Promise.all(['400 40px Boldonse', "500 20px 'JetBrains Mono'", '600 20px Switzer'].map(function (f) { return document.fonts.load(f).catch(function () {}); })).then(cb);
}
var NOISE="float hash(vec2 p){p=fract(p*vec2(123.34,456.21));p+=dot(p,p+45.32);return fract(p.x*p.y);}float vnoise(vec2 p){vec2 i=floor(p),f=fract(p);f=f*f*(3.-2.*f);return mix(mix(hash(i),hash(i+vec2(1.,0.)),f.x),mix(hash(i+vec2(0.,1.)),hash(i+vec2(1.,1.)),f.x),f.y);}float fbm(vec2 p){float v=0.,a=.5;for(int i=0;i<5;i++){v+=a*vnoise(p);p*=2.03;a*=.5;}return v;}";
var w = root.querySelector('.lhm'); if (!w) return;
var fb = w.querySelector('.lhm__fb'), cv = w.querySelector('.lhm__gl');
function logo(c, blur) {
var x = c.getContext('2d'), W = c.width, H = c.height;
x.fillStyle = '#000'; x.fillRect(0, 0, W, H); x.fillStyle = '#fff'; x.strokeStyle = '#fff';
x.shadowColor = '#fff'; x.shadowBlur = blur ? H * .1 : 0;
for (var i = 0; i < (blur ? 3 : 1); i++) {
x.lineWidth = H * .045; x.beginPath(); x.arc(W * .5, H * .34, H * .13, 0, 7); x.stroke();
x.fillRect(W * .5 - H * .2, H * .335, H * .4, H * .03);
x.font = '400 ' + Math.round(H * .15) + 'px Boldonse, Switzer, sans-serif'; x.textAlign = 'center'; x.textBaseline = 'middle';
x.fillText('KADE', W * .5, H * .7);
}
}
var a = document.createElement('canvas'), b = document.createElement('canvas');
a.width = b.width = 800; a.height = b.height = 500; logo(a, false); logo(b, true); fb.appendChild(a);
var g = mkGL(cv, NOISE + `uniform sampler2D uA,uB;uniform vec2 uRes,uM;uniform float uT;
void main(){
vec2 uv=vUv;
float n=fbm(uv*vec2(4.,3.)+vec2(0.,-uT*.35));
float n2=fbm(uv*9.+vec2(uT*.1,-uT*.6));
float m=texture2D(uA,uv).r;
float g=texture2D(uB,uv-vec2(0.,.05*n)).r;
vec2 a=vec2(uRes.x/uRes.y,1.);float d=distance(uv*a,uM*a);
float heat=g*(.5+.75*n)+m*(.62+.22*sin(uT*1.6+uv.x*7.))+.45*exp(-d*d*14.)*(.6+.6*n2)+n2*.08;
vec3 col=vec3(.039);
col=mix(col,vec3(.36,.05,.02),smoothstep(.06,.32,heat));
col=mix(col,vec3(.961,.282,.059),smoothstep(.28,.62,heat));
col=mix(col,vec3(1.,.74,.3),smoothstep(.6,.88,heat));
col=mix(col,vec3(1.,.97,.9),smoothstep(.9,1.15,heat));
gl_FragColor=vec4(col,1.);
}`);
if (!g) { w.classList.add('is-nogl'); return; }
var ta = g.tex(a, 0, 'uA'), tb = g.tex(b, 1, 'uB'), mx = .5, my = -1, ax = .5, ay = -1, L;
w.addEventListener('pointermove', function (e) { var r = w.getBoundingClientRect(); ax = (e.clientX - r.left) / r.width; ay = 1 - (e.clientY - r.top) / r.height; L.kick(); });
w.addEventListener('pointerleave', function () { ay = -1; L.kick(); });
L = runLoop(w, function (t) {
if (L && L.reduce) { mx = ax; my = ay; } else { mx += (ax - mx) * .1; my += (ay - my) * .1; }
g.fit(); g.gl.uniform1f(g.u('uT'), t); g.gl.uniform2f(g.u('uM'), mx, my); g.draw();
});
fontsThen(function () { logo(a, false); logo(b, true); g.upd(ta, a, 0); g.upd(tb, b, 1); L.kick(); });
}
init(document.querySelector('.jouw-container'));
More backgrounds & webgl
Seen something you like?
Add it to your selection and send it over. We build your site around it, in your brand.
Get in touch →