Live demo, running in your browser

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 &amp; 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 &amp; 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 &middot; Warm &middot; 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.