Live demo, running in your browser

Porcelain octopus

A cream editorial hero with a giant DRIFT wordmark where a WebGL-rippled floating subject weaves between the back and front letters, with scrolling outlined ghost text, drifting dust and bokeh, and pointer-driven parallax.

Assets

Code: MIT license. Imagery generated with Google Gemini

<!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>Drift</title
    ><script type="importmap">
      { "imports": {} }</script
    ><style>
      html,
      body {
        margin: 0;
        height: 100%;
        background: #f2e8d5;
        overflow: hidden;
      }
      * {
        box-sizing: border-box;
      }
      #hero {
        position: relative;
        width: 100vw;
        height: 100vh;
        overflow: hidden;
        background: radial-gradient(120% 90% at 55% 40%, #f6eddb 0%, #f2e8d5 55%, #e9dcc2 100%);
        font-family:
          system-ui,
          -apple-system,
          "Segoe UI",
          Helvetica,
          Arial,
          sans-serif;
        color: #14130f;
      }
      .layer {
        position: absolute;
        inset: 0;
        will-change: transform;
        pointer-events: none;
      }
      #ghost {
        z-index: 1;
      }
      .grow {
        position: absolute;
        left: 0;
        white-space: nowrap;
        font-weight: 900;
        font-size: 17vh;
        line-height: 1;
        letter-spacing: -0.03em;
        color: transparent;
        -webkit-text-stroke: 1.2px rgba(20, 19, 15, 0.16);
        font-family: "Arial Black", Impact, "Helvetica Neue", system-ui, sans-serif;
      }
      .grow span {
        display: inline-block;
        margin-right: 0.35em;
      }
      #dust {
        z-index: 2;
      }
      #back {
        z-index: 3;
      }
      #octo {
        position: absolute;
        z-index: 4;
        left: 56%;
        top: 46%;
        opacity: 0;
        transition: opacity 1.4s ease;
        pointer-events: none;
        will-change: transform;
      }
      #octo.on {
        opacity: 1;
      }
      #octo canvas,
      #octo img {
        width: 100%;
        height: 100%;
        display: block;
        filter: drop-shadow(0 3vh 3.5vh rgba(20, 19, 15, 0.22));
      }
      #front {
        z-index: 5;
      }
      .word {
        position: absolute;
        left: 50%;
        top: 47%;
        transform: translate(-50%, -50%);
        white-space: nowrap;
        text-align: center;
        font-family: "Arial Black", Impact, "Helvetica Neue", system-ui, sans-serif;
        font-weight: 900;
        letter-spacing: -0.05em;
        line-height: 0.8;
        color: #14130f;
        font-size: 100px;
      }
      .word span {
        display: inline-block;
      }
      .word .br {
        display: none;
      }
      .word.stack .br {
        display: block;
      }
      #bokeh {
        z-index: 6;
      }
      .ui {
        position: absolute;
        z-index: 10;
      }
      .brand {
        top: 28px;
        left: 32px;
        font-weight: 800;
        letter-spacing: 0.14em;
        font-size: 13px;
        text-transform: uppercase;
      }
      .tag {
        top: 28px;
        right: 32px;
        font-size: 13px;
        letter-spacing: 0.08em;
        opacity: 0.65;
      }
      .copy {
        left: 32px;
        bottom: 36px;
        max-width: 360px;
      }
      .copy p {
        margin: 0 0 18px;
        font-size: 17px;
        line-height: 1.45;
        font-weight: 500;
      }
      .btn {
        display: inline-flex;
        align-items: center;
        gap: 10px;
        border: 0;
        cursor: pointer;
        background: #14130f;
        color: #f2e8d5;
        font: inherit;
        font-weight: 700;
        font-size: 15px;
        padding: 15px 26px;
        border-radius: 999px;
        transition:
          background 0.25s,
          transform 0.25s;
      }
      .btn:hover {
        background: #1c48c4;
        transform: translateY(-2px);
      }
      .btn i {
        display: inline-block;
        width: 8px;
        height: 8px;
        border-radius: 50%;
        background: #2a5bff;
      }
      .btn:hover i {
        background: #f2e8d5;
      }
      .nogl #octo img {
        animation: bob 6s ease-in-out infinite;
      }
      @keyframes bob {
        50% {
          transform: translateY(-3%) rotate(2deg);
        }
      }
      @media (max-aspect-ratio: 11/10) {
        .brand {
          top: 20px;
          left: 22px;
        }
        .tag {
          top: 20px;
          right: 22px;
          font-size: 11px;
        }
        .copy {
          left: 22px;
          bottom: 28px;
          max-width: 300px;
        }
        .copy p {
          font-size: 15px;
          margin-bottom: 14px;
        }
        .grow {
          font-size: 11vh;
        }
      }
    </style></head
  ><body
    ><div id="hero"
      ><div id="ghost" class="layer"
        ><div class="grow" id="g1" style="top: 5%"></div
        ><div class="grow" id="g2" style="top: 68%"></div></div
      ><canvas id="dust" class="layer"></canvas
      ><div id="back" class="layer"><div class="word" id="bw"></div></div><div id="octo"></div
      ><div id="front" class="layer"><div class="word" id="fw" aria-hidden="true"></div></div
      ><canvas id="bokeh" class="layer"></canvas><div class="ui brand">Tidewell</div
      ><div class="ui tag">Vol. 01 &mdash; slow software</div
      ><div class="ui copy"
        ><p>Hand-glazed focus tools that keep your day gently afloat.</p
        ><button class="btn" type="button"><i></i>Start drifting</button></div
      ></div
    ><script>
      (function () {
        var P = window.__pattern || { paused: false, reducedMotion: false, maxDpr: 2 };
        var hero = document.getElementById("hero"),
          back = document.getElementById("back"),
          front = document.getElementById("front"),
          bw = document.getElementById("bw"),
          fw = document.getElementById("fw"),
          octo = document.getElementById("octo"),
          g1 = document.getElementById("g1"),
          g2 = document.getElementById("g2"),
          dustC = document.getElementById("dust"),
          bokC = document.getElementById("bokeh");
        var WORD = "DRIFT",
          FRONT = [1, 4];
        function build(el, isF) {
          el.innerHTML = "";
          for (var i = 0; i < WORD.length; i++) {
            var s = document.createElement("span");
            s.textContent = WORD[i];
            if (isF && FRONT.indexOf(i) < 0) s.style.visibility = "hidden";
            el.appendChild(s);
            if (i === 2) {
              var b = document.createElement("br");
              b.className = "br";
              el.appendChild(b);
            }
          }
        }
        build(bw, false);
        build(fw, true);
        function fillGhost(g) {
          var h = "";
          for (var i = 0; i < 10; i++) h += "<span>DRIFT</span>";
          g.innerHTML = h;
        }
        fillGhost(g1);
        fillGhost(g2);
        var S = 600,
          port = false,
          unitW = 300,
          W = innerWidth,
          H = innerHeight,
          dpr = 1;
        function getDpr() {
          return Math.max(1, Math.min(window.devicePixelRatio || 1, P.maxDpr || 2));
        }
        var gl = null,
          oc = null,
          prog = null,
          tex = null,
          uT = null,
          loaded = false,
          dirty = true;
        function layout() {
          W = innerWidth;
          H = innerHeight;
          dpr = getDpr();
          port = H > W * 1.1;
          var st = port;
          bw.classList.toggle("stack", st);
          fw.classList.toggle("stack", st);
          bw.style.fontSize = "100px";
          fw.style.fontSize = "100px";
          var w = bw.getBoundingClientRect().width || 300;
          var fs = (100 * W * (port ? 0.92 : 0.95)) / w;
          fs = Math.min(fs, H * 0.72);
          bw.style.fontSize = fs + "px";
          fw.style.fontSize = fs + "px";
          var top = port ? "36%" : "47%";
          bw.style.top = top;
          fw.style.top = top;
          S = port ? W * 1.05 : Math.min(H * 1.02, W * 0.72);
          octo.style.width = S + "px";
          octo.style.height = S + "px";
          octo.style.left = port ? "50%" : "57%";
          octo.style.top = port ? "41%" : "47%";
          if (oc) {
            oc.width = Math.round(S * dpr);
            oc.height = Math.round(S * dpr);
          }
          var u = g1.querySelector("span");
          unitW = u
            ? u.getBoundingClientRect().width + parseFloat(getComputedStyle(u).marginRight || 0)
            : 300;
          dustC.width = Math.round(W * dpr);
          dustC.height = Math.round(H * dpr);
          bokC.width = Math.round(W * dpr);
          bokC.height = Math.round(H * dpr);
          dirty = true;
        }
        function mk(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 initGL() {
          oc = document.createElement("canvas");
          try {
            gl = oc.getContext("webgl", {
              alpha: true,
              premultipliedAlpha: true,
              antialias: false,
            });
          } catch (e) {
            gl = null;
          }
          if (!gl) return false;
          try {
            var vs =
              "attribute vec2 p;varying vec2 v;void main(){v=p*.5+.5;gl_Position=vec4(p,0.,1.);}";
            var fs =
              "precision mediump float;varying vec2 v;uniform sampler2D tx;uniform float t;void main(){vec2 uv=vec2(v.x,1.-v.y);vec2 c=uv-vec2(.5,.45);float br=1.+sin(t*.9)*.008;uv=vec2(.5,.45)+c/br;float w=smoothstep(.38,.98,uv.y);float w2=w*w;uv.x+=sin(uv.y*7.-t*1.5+uv.x*3.)*.016*w+sin(uv.y*13.-t*2.1)*.004*w2;uv.y+=sin(uv.x*6.+t*1.15)*.01*w;vec4 col=texture2D(tx,uv);float lum=dot(col.rgb,vec3(.333));float sh=pow(max(0.,sin((uv.x*1.2+uv.y)*5.-t*.6)),24.)*.16*smoothstep(.6,.95,lum);col.rgb=min(col.rgb+vec3(sh)*col.a,vec3(1.));gl_FragColor=col;}";
            prog = gl.createProgram();
            gl.attachShader(prog, mk(gl.VERTEX_SHADER, vs));
            gl.attachShader(prog, mk(gl.FRAGMENT_SHADER, fs));
            gl.linkProgram(prog);
            if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) throw new Error("link");
            gl.useProgram(prog);
            var buf = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, buf);
            gl.bufferData(
              gl.ARRAY_BUFFER,
              new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]),
              gl.STATIC_DRAW,
            );
            var loc = gl.getAttribLocation(prog, "p");
            gl.enableVertexAttribArray(loc);
            gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
            uT = gl.getUniformLocation(prog, "t");
            gl.uniform1i(gl.getUniformLocation(prog, "tx"), 0);
            gl.enable(gl.BLEND);
            gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
          } catch (e) {
            gl = null;
            return false;
          }
          octo.appendChild(oc);
          return true;
        }
        var hasGL = initGL();
        var img = new Image();
        img.crossOrigin = "anonymous";
        img.onload = function () {
          if (hasGL && gl) {
            try {
              tex = gl.createTexture();
              gl.activeTexture(gl.TEXTURE0);
              gl.bindTexture(gl.TEXTURE_2D, tex);
              gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
              gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
              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);
              loaded = true;
            } catch (e) {
              hasGL = false;
              gl = null;
            }
          }
          if (!hasGL || !gl) {
            hero.classList.add("nogl");
            if (oc && oc.parentNode) oc.parentNode.removeChild(oc);
            octo.appendChild(img);
            loaded = true;
          }
          octo.classList.add("on");
          dirty = true;
        };
        img.onerror = function () {};
        img.src = "https://agents-dev.6to9.ai/visuals/0ed80ccfe9c09aa4d198c8e827139a35.png";
        if (!hasGL) {
          hero.classList.add("nogl");
        }
        var dust = [],
          bok = [],
          i;
        for (i = 0; i < 80; i++)
          dust.push({
            x: Math.random(),
            y: Math.random(),
            r: 0.6 + Math.random() * 1.6,
            vy: 0.004 + Math.random() * 0.01,
            vx: (Math.random() - 0.5) * 0.004,
            a: 0.15 + Math.random() * 0.35,
            ph: Math.random() * 6.28,
            c: Math.random() < 0.35 ? "24,64,190" : "20,19,15",
          });
        var bcols = ["24,64,190", "24,64,190", "214,150,90", "255,252,240"];
        for (i = 0; i < 12; i++)
          bok.push({
            x: Math.random(),
            y: Math.random(),
            r: 22 + Math.random() * 60,
            vy: 0.003 + Math.random() * 0.008,
            vx: (Math.random() - 0.5) * 0.004,
            a: 0.5 + Math.random() * 0.6,
            ph: Math.random() * 6.28,
            c: bcols[i % bcols.length],
          });
        var dx = dustC.getContext("2d"),
          bx = bokC.getContext("2d");
        function drawParts(t, dt) {
          dx.setTransform(1, 0, 0, 1, 0, 0);
          dx.clearRect(0, 0, dustC.width, dustC.height);
          dx.scale(dpr, dpr);
          var k, p;
          for (k = 0; k < dust.length; k++) {
            p = dust[k];
            p.y -= p.vy * dt;
            p.x += p.vx * dt + Math.sin(t * 0.4 + p.ph) * 0.00012 * (dt > 0 ? 1 : 0);
            if (p.y < -0.02) p.y = 1.02;
            if (p.x < -0.02) p.x = 1.02;
            if (p.x > 1.02) p.x = -0.02;
            var tw = 0.6 + 0.4 * Math.sin(t * 0.8 + p.ph);
            dx.fillStyle = "rgba(" + p.c + "," + p.a * tw + ")";
            dx.beginPath();
            dx.arc(p.x * W, p.y * H, p.r, 0, 6.283);
            dx.fill();
          }
          bx.setTransform(1, 0, 0, 1, 0, 0);
          bx.clearRect(0, 0, bokC.width, bokC.height);
          bx.scale(dpr, dpr);
          for (k = 0; k < bok.length; k++) {
            p = bok[k];
            p.y -= p.vy * dt;
            p.x += p.vx * dt;
            if (p.y < -0.1) p.y = 1.1;
            if (p.x < -0.1) p.x = 1.1;
            if (p.x > 1.1) p.x = -0.1;
            var px = p.x * W,
              py = p.y * H,
              r = p.r * (port ? 0.7 : 1);
            var g = bx.createRadialGradient(px, py, 0, px, py, r);
            var al = 0.16 * p.a * (0.7 + 0.3 * Math.sin(t * 0.5 + p.ph));
            g.addColorStop(0, "rgba(" + p.c + "," + al * 0.35 + ")");
            g.addColorStop(0.78, "rgba(" + p.c + "," + al * 0.8 + ")");
            g.addColorStop(0.94, "rgba(" + p.c + "," + al * 1.7 + ")");
            g.addColorStop(1, "rgba(" + p.c + ",0)");
            bx.fillStyle = g;
            bx.beginPath();
            bx.arc(px, py, r, 0, 6.283);
            bx.fill();
          }
        }
        var tx = 0,
          ty = 0,
          mx = 0,
          my = 0;
        window.addEventListener("pointermove", function (e) {
          tx = (e.clientX / W) * 2 - 1;
          ty = (e.clientY / H) * 2 - 1;
        });
        function update(t, dt) {
          mx += (tx - mx) * Math.min(1, dt * 3);
          my += (ty - my) * Math.min(1, dt * 3);
          var a = Math.sin(t * 0.23) * 0.6 + mx,
            b = Math.cos(t * 0.31) * 0.4 + my;
          var tr = "translate3d(" + (-a * 14).toFixed(2) + "px," + (-b * 8).toFixed(2) + "px,0)";
          back.style.transform = tr;
          front.style.transform = tr;
          var bob = Math.sin(t * 0.7) * S * 0.016 + Math.sin(t * 1.3) * S * 0.004;
          octo.style.transform =
            "translate(-50%,-50%) translate3d(" +
            (a * 32 + Math.sin(t * 0.17) * S * 0.02).toFixed(2) +
            "px," +
            (b * 18 + bob).toFixed(2) +
            "px,0) rotate(" +
            (Math.sin(t * 0.4) * 2.2).toFixed(3) +
            "deg)";
          var off = (t * 22) % unitW;
          g1.style.transform =
            "translate3d(" + (-off + a * -6 - unitW).toFixed(1) + "px," + -b * 4 + "px,0)";
          g2.style.transform =
            "translate3d(" + (-unitW * 2 + off + a * -6).toFixed(1) + "px," + -b * 4 + "px,0)";
          dustC.style.transform =
            "translate3d(" + (-a * 6).toFixed(1) + "px," + (-b * 4).toFixed(1) + "px,0)";
          bokC.style.transform =
            "translate3d(" + (a * 46).toFixed(1) + "px," + (b * 28).toFixed(1) + "px,0)";
          if (gl && loaded && tex) {
            gl.viewport(0, 0, oc.width, oc.height);
            gl.clearColor(0, 0, 0, 0);
            gl.clear(gl.COLOR_BUFFER_BIT);
            gl.uniform1f(uT, t);
            gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
          }
          drawParts(t, dt);
        }
        var last = performance.now(),
          T = 0;
        function frame(now) {
          requestAnimationFrame(frame);
          var dt = Math.min(0.05, Math.max(0, (now - last) / 1000));
          last = now;
          if (P.paused) return;
          if (P.reducedMotion) {
            if (!dirty) return;
            T = 3;
            dt = 0;
          } else T += dt;
          dirty = false;
          try {
            update(T, dt);
          } catch (e) {}
        }
        window.addEventListener("resize", layout);
        layout();
        requestAnimationFrame(frame);
      })();
    </script></body
  ></html
>

Made by 6to9. Back to all motion patterns.