Grain mesh gradient
A warm coral, amber and plum editorial hero where a WebGL field of 2,600 soft grainy dots drifts slowly as blended color blobs behind centered serif headline text, with a film-grain overlay and a still frame for reduced motion.
Code: MIT license.
<!doctype html>
<html lang="en"
><head
><script>
/* 6to9 pattern harness */ (function () {
var parentPaused = false,
hidden = document.hidden,
p = (window.__pattern = { paused: false, reducedMotion: false, maxDpr: 2 });
function sync() {
p.paused = parentPaused || hidden;
}
sync();
try {
p.reducedMotion = matchMedia("(prefers-reduced-motion: reduce)").matches;
} catch (e) {}
document.addEventListener("visibilitychange", function () {
hidden = document.hidden;
sync();
});
addEventListener("message", function (e) {
var d = e.data || {};
if (d.pattern === "pause") {
parentPaused = true;
sync();
}
if (d.pattern === "play") {
parentPaused = false;
sync();
}
});
})();</script
><meta charset="utf-8" /><meta
name="viewport"
content="width=device-width,initial-scale=1"
/><title>Folio & Ember</title
><style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body {
width: 100%;
height: 100%;
overflow: hidden;
background: #3a1230;
}
body {
font-family: Georgia, "Times New Roman", "Iowan Old Style", serif;
color: #fff4e6;
}
.hero {
position: relative;
width: 100vw;
height: 100vh;
height: 100dvh;
overflow: hidden;
background: radial-gradient(60% 55% at 20% 25%, #ff6b57 0%, transparent 70%),
radial-gradient(55% 50% at 80% 30%, #ffb27a 0%, transparent 70%),
radial-gradient(60% 55% at 70% 85%, #efdcbc 0%, transparent 70%),
linear-gradient(160deg, #5a1d3f, #3a1230);
}
canvas {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
display: none;
}
.hero.gl canvas {
display: block;
}
.veil {
position: absolute;
inset: 0;
background: radial-gradient(
ellipse 60% 45% at 50% 50%,
rgba(48, 12, 36, 0.42),
rgba(48, 12, 36, 0) 75%
);
pointer-events: none;
}
.nav {
position: absolute;
top: 0;
left: 0;
right: 0;
display: flex;
justify-content: space-between;
align-items: center;
padding: 28px 4vw;
font-size: 13px;
letter-spacing: 0.22em;
text-transform: uppercase;
text-shadow: 0 1px 12px rgba(48, 12, 36, 0.5);
}
.nav b {
font-weight: 400;
font-style: italic;
letter-spacing: 0.02em;
font-size: 22px;
text-transform: none;
}
.center {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 0 6vw;
}
.kicker {
font-size: 12px;
letter-spacing: 0.34em;
text-transform: uppercase;
margin-bottom: 2.2vh;
opacity: 0.92;
text-shadow: 0 1px 12px rgba(48, 12, 36, 0.6);
}
h1 {
font-weight: 400;
font-size: clamp(54px, 11.5vw, 180px);
line-height: 0.9;
letter-spacing: -0.035em;
text-shadow: 0 2px 40px rgba(60, 10, 40, 0.35);
}
h1 em {
font-style: italic;
font-weight: 400;
}
.sub {
margin-top: 3vh;
max-width: 30em;
font-size: clamp(15px, 1.5vw, 20px);
line-height: 1.5;
opacity: 0.95;
text-shadow: 0 1px 14px rgba(48, 12, 36, 0.6);
}
.btn {
margin-top: 4vh;
display: inline-block;
padding: 15px 34px;
border: 1px solid rgba(255, 244, 230, 0.85);
border-radius: 999px;
color: #fff4e6;
text-decoration: none;
font-size: 12px;
letter-spacing: 0.26em;
text-transform: uppercase;
background: rgba(58, 18, 48, 0.25);
transition:
background 0.3s,
color 0.3s;
}
.btn:hover {
background: #fff4e6;
color: #3a1230;
}
.foot {
position: absolute;
left: 4vw;
right: 4vw;
bottom: 26px;
display: flex;
justify-content: space-between;
font-size: 11px;
letter-spacing: 0.24em;
text-transform: uppercase;
opacity: 0.85;
text-shadow: 0 1px 10px rgba(48, 12, 36, 0.6);
}
@media (max-width: 600px) {
.nav {
padding: 22px 6vw;
font-size: 11px;
}
.foot {
bottom: 20px;
font-size: 10px;
}
h1 {
font-size: clamp(52px, 17vw, 100px);
}
}
</style></head
><body
><section class="hero" id="hero"
><canvas id="c"></canvas><div class="veil"></div
><div class="nav"><b>Folio & Ember</b><span>Issue 07</span></div
><div class="center"
><div class="kicker">The Spring Edition</div><h1>Warmth, in <em>every</em> issue.</h1
><p class="sub"
>A quiet collection of stories, objects and slow-burning ideas, printed in the colour of
late afternoon.</p
><a class="btn" href="#">Begin reading</a></div
><div class="foot"
><span>Vol. 07</span><span>Slow · Warm · Analogue</span></div
></section
><script>
(function () {
var hero = document.getElementById("hero");
var cv = document.getElementById("c");
var gl = null;
try {
gl = cv.getContext("webgl", { antialias: false, alpha: false });
} catch (e) {
gl = null;
}
if (!gl) {
return;
}
try {
var N = 2600;
function sh(type, src) {
var s = gl.createShader(type);
gl.shaderSource(s, src);
gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
throw new Error("shader");
}
return s;
}
function prog(v, f) {
var p = gl.createProgram();
gl.attachShader(p, sh(gl.VERTEX_SHADER, v));
gl.attachShader(p, sh(gl.FRAGMENT_SHADER, f));
gl.linkProgram(p);
if (!gl.getProgramParameter(p, gl.LINK_STATUS)) {
throw new Error("link");
}
return p;
}
var vs =
"precision highp float;attribute vec3 a_seed;uniform float u_t;uniform float u_aspect;uniform float u_size;varying vec3 v_col;vec3 C0=vec3(1.0,0.36,0.30);vec3 C1=vec3(1.0,0.70,0.45);vec3 C2=vec3(0.94,0.86,0.70);vec3 C3=vec3(0.23,0.07,0.19);float W(vec2 q,vec2 b,float r,float s){vec2 d=q-b;return s*exp(-dot(d,d)/(r*r));}void main(){vec2 p=a_seed.xy*2.0-1.0;float ph=a_seed.z*6.2831;p+=0.03*vec2(sin(u_t*0.35+ph+p.y*3.0),cos(u_t*0.3+ph+p.x*3.0));vec2 q=p*vec2(u_aspect,1.0);float A=min(u_aspect,1.3);float t=u_t*0.16;vec2 b0=vec2(sin(t*1.1+0.5)*0.75*A,cos(t*0.9+1.0)*0.65);vec2 b1=vec2(sin(t*0.8+2.4)*0.8*A,cos(t*1.2+2.0)*0.7);vec2 b2=vec2(sin(t*1.0+4.1)*0.8*A,cos(t*0.7+4.0)*0.7);vec2 b3=vec2(sin(t*0.7+5.6)*0.7*A,cos(t*1.1+5.2)*0.75);float r=0.62+0.1*(1.0-min(u_aspect,1.0));float w0=W(q,b0,r,1.0);float w1=W(q,b1,r*1.05,1.0);float w2=W(q,b2,r*0.95,0.95);float w3=W(q,b3,r*1.25,1.5);float base=0.02;float sum=w0+w1+w2+w3+base;vec3 c=(w0*C0+w1*C1+w2*C2+w3*C3+base*C3)/sum;float h=fract(sin(dot(a_seed.xy,vec2(12.9898,78.233)))*43758.5453);c*=0.94+0.12*h;v_col=c;gl_Position=vec4(p,0.0,1.0);gl_PointSize=u_size;}";
var fs =
"precision mediump float;varying vec3 v_col;void main(){float d=length(gl_PointCoord-0.5);float a=smoothstep(0.5,0.0,d)*0.6;gl_FragColor=vec4(v_col,a);}";
var gvs = "attribute vec2 a_pos;void main(){gl_Position=vec4(a_pos,0.0,1.0);}";
var gfs =
"precision mediump float;uniform float u_f;void main(){vec2 p=gl_FragCoord.xy+u_f*vec2(37.0,17.0);float n=fract(sin(dot(p,vec2(12.9898,78.233)))*43758.5453);float v=n-0.5;gl_FragColor=vec4(vec3(step(0.0,v)),abs(v)*0.45);}";
var pP = prog(vs, fs);
var pG = prog(gvs, gfs);
var seeds = new Float32Array(N * 3);
for (var i = 0; i < N * 3; i++) {
seeds[i] = Math.random();
}
var bufS = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, bufS);
gl.bufferData(gl.ARRAY_BUFFER, seeds, gl.STATIC_DRAW);
var bufQ = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, bufQ);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
var aSeed = gl.getAttribLocation(pP, "a_seed");
var aPos = gl.getAttribLocation(pG, "a_pos");
var uT = gl.getUniformLocation(pP, "u_t");
var uA = gl.getUniformLocation(pP, "u_aspect");
var uS = gl.getUniformLocation(pP, "u_size");
var uF = gl.getUniformLocation(pG, "u_f");
var maxPt = 64;
try {
var rg = gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE);
if (rg && rg[1]) maxPt = rg[1];
} catch (e) {}
var CW = 2,
CH = 2,
t = 14,
frameN = 0,
need = true,
still = false,
last = performance.now(),
lastDraw = 0;
function resize() {
var P = window.__pattern || {};
var md = P.maxDpr || 1;
var dpr = Math.max(0.5, Math.min(window.devicePixelRatio || 1, md, 1));
var sc = 0.5 * dpr;
var w = Math.max(1, hero.clientWidth),
h = Math.max(1, hero.clientHeight);
CW = Math.max(2, Math.floor(w * sc));
CH = Math.max(2, Math.floor(h * sc));
cv.width = CW;
cv.height = CH;
gl.viewport(0, 0, CW, CH);
need = true;
}
function draw() {
gl.viewport(0, 0, CW, CH);
gl.clearColor(0.23, 0.07, 0.19, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.useProgram(pP);
gl.bindBuffer(gl.ARRAY_BUFFER, bufS);
gl.enableVertexAttribArray(aSeed);
gl.vertexAttribPointer(aSeed, 3, gl.FLOAT, false, 0, 0);
gl.uniform1f(uT, t);
gl.uniform1f(uA, CW / CH);
var sz = Math.min(maxPt, Math.sqrt((CW * CH) / N) * 2.6);
gl.uniform1f(uS, sz);
gl.drawArrays(gl.POINTS, 0, N);
gl.disableVertexAttribArray(aSeed);
gl.useProgram(pG);
gl.bindBuffer(gl.ARRAY_BUFFER, bufQ);
gl.enableVertexAttribArray(aPos);
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
gl.uniform1f(uF, (Math.floor(frameN / 2) % 60) + 1);
gl.drawArrays(gl.TRIANGLES, 0, 3);
gl.disableVertexAttribArray(aPos);
}
function frame(now) {
requestAnimationFrame(frame);
try {
var P = window.__pattern || {};
if (P.reducedMotion) {
if (!still || need) {
draw();
still = true;
need = false;
}
last = now;
return;
}
still = false;
if (P.paused) {
last = now;
return;
}
if (now - lastDraw < 50 && !need) {
return;
}
lastDraw = now;
var dt = Math.min(0.1, (now - last) / 1000);
last = now;
t += dt;
frameN++;
need = false;
draw();
} catch (e) {}
}
window.addEventListener("resize", resize);
resize();
hero.className = "hero gl";
requestAnimationFrame(frame);
} catch (e) {
hero.className = "hero";
}
})();
</script></body
></html
>
Made by 6to9. Back to all motion patterns.