Live demo, running in your browser

Brass hummingbird

A giant mint-green SWIFT wordmark with a clockwork hummingbird sprite that flaps, hovers, and darts between letters along curved paths, weaving in front of some letters and behind others.

Assets

Code: MIT license. three.js 0.184.0 (MIT) · 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>Swift — clockwork hummingbird</title
    ><style>
      html,
      body {
        margin: 0;
        height: 100%;
        background: #9fe8c1;
        overflow: hidden;
      }
      .hero {
        position: relative;
        width: 100vw;
        height: 100vh;
        height: 100svh;
        overflow: hidden;
        background: radial-gradient(120% 90% at 50% 40%, #aef2cb 0%, #9fe8c1 55%, #8fdcb3 100%);
        font-family: "Helvetica Neue", Arial, system-ui, sans-serif;
        color: #0d3b2a;
      }
      .word {
        position: absolute;
        left: 0;
        right: 0;
        top: 44%;
        transform: translateY(-50%);
        text-align: center;
        font-family: "Arial Black", "Helvetica Neue", Impact, system-ui, sans-serif;
        font-weight: 900;
        font-size: min(28vw, 60vh);
        line-height: 0.82;
        letter-spacing: -0.05em;
        white-space: nowrap;
        margin: 0;
        user-select: none;
        -webkit-user-select: none;
      }
      .word span {
        display: inline-block;
      }
      .back {
        z-index: 1;
      }
      .front {
        z-index: 3;
        pointer-events: none;
      }
      .front .o {
        color: transparent;
      }
      canvas {
        position: absolute;
        left: 0;
        top: 0;
        width: 100%;
        height: 100%;
        z-index: 2;
        display: block;
        pointer-events: none;
      }
      .m {
        display: none;
      }
      .nav {
        position: absolute;
        z-index: 4;
        left: 0;
        right: 0;
        top: 0;
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 22px 5vw;
        font-size: 14px;
        font-weight: 700;
        letter-spacing: 0.14em;
        text-transform: uppercase;
      }
      .nav a {
        color: inherit;
        text-decoration: none;
        opacity: 0.8;
      }
      .info {
        position: absolute;
        z-index: 4;
        left: 0;
        right: 0;
        bottom: 6vh;
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 16px;
        text-align: center;
        padding: 0 6vw;
      }
      .info p {
        margin: 0;
        font-size: clamp(15px, 1.5vw, 20px);
        font-weight: 600;
        max-width: 34ch;
        line-height: 1.35;
      }
      .btn {
        appearance: none;
        border: 0;
        background: #0d3b2a;
        color: #c9f7dd;
        font:
          700 15px system-ui,
          sans-serif;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        padding: 15px 30px;
        border-radius: 99px;
        cursor: pointer;
        transition:
          transform 0.2s,
          background 0.2s;
      }
      .btn:hover {
        transform: translateY(-2px);
        background: #145239;
      }
      .fb {
        display: none;
        position: absolute;
        z-index: 2;
        left: 50%;
        top: 44%;
        width: 90px;
        height: 90px;
        margin: -45px 0 0 -45px;
        border-radius: 50%;
        background: radial-gradient(circle at 35% 30%, #ffe9a8, #c9923a 55%, #7a4e16);
        box-shadow: 0 0 0 10px rgba(201, 146, 58, 0.25);
        animation: fb 2.4s ease-in-out infinite;
      }
      @keyframes fb {
        0%,
        100% {
          transform: translate(-120px, -20px);
        }
        50% {
          transform: translate(120px, 20px);
        }
      }
      .nogl .fb {
        display: block;
      }
      @media (max-aspect-ratio: 1/1) {
        .word {
          font-size: 44vw;
          top: 40%;
        }
        .m {
          display: block;
          height: 0;
        }
        .nav {
          padding: 18px 6vw;
        }
      }</style
    ><script type="importmap">
      { "imports": { "three": "https://cdn.jsdelivr.net/npm/three@0.184.0/build/three.module.js" } }
    </script></head
  ><body
    ><main class="hero nogl" id="hero"
      ><div class="nav"><span>Brasswing</span><a href="#">Workshop</a></div
      ><h1 class="word back" id="wb"
        ><span>S</span><span>W</span><span>I</span><i class="m"></i><span>F</span><span>T</span></h1
      ><div class="fb"></div
      ><div class="word front" aria-hidden="true"
        ><span class="o">S</span><span>W</span><span class="o">I</span><i class="m"></i
        ><span>F</span><span class="o">T</span></div
      ><div class="info"
        ><p>Hand-wound precision that hovers, darts and never misses a beat.</p
        ><button class="btn">Wind it up</button></div
      ></main
    ><script type="module">
      import * as THREE from "three";
      const P = window.__pattern || { paused: false, reducedMotion: false, maxDpr: 2 };
      const hero = document.getElementById("hero");
      const letters = Array.prototype.slice.call(document.querySelectorAll("#wb span"));
      let renderer = null;
      try {
        renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
      } catch (e) {
        renderer = null;
      }
      if (renderer) {
        try {
          init();
        } catch (e) {
          hero.classList.add("nogl");
        }
      }
      function init() {
        renderer.setClearColor(0x000000, 0);
        hero.insertBefore(renderer.domElement, hero.querySelector(".front"));
        const scene = new THREE.Scene();
        const camera = new THREE.OrthographicCamera(0, 1, 1, 0, -10, 10);
        const geo = new THREE.PlaneGeometry(1, 1, 24, 24);
        const vs =
          "uniform float uTime;uniform vec2 uVel;varying vec2 vUv;void main(){vUv=uv;vec3 p=position;p*=1.0+sin(uTime*3.1)*0.012;p.y+=sin(uTime*2.4)*0.012;p.x+=sin(uv.y*7.0+uTime*5.0)*0.004;p.y+=sin(uv.x*6.0+uTime*4.0)*0.004;p.x-=uVel.x*0.00006*(0.5-uv.y);p.y+=uVel.y*0.00003*(0.5-uv.x)*0.0;p.y-=uVel.y*0.00004*(uv.y-0.5)*0.5;gl_Position=projectionMatrix*modelViewMatrix*vec4(p,1.0);}";
        const fs =
          "precision highp float;uniform sampler2D map;uniform float uFrame;uniform float uNext;uniform float uMix;varying vec2 vUv;vec4 fr(float f){float i=0.0015;float u=(f+i+vUv.x*(1.0-2.0*i))/8.0;float v=0.004+vUv.y*0.992;return texture2D(map,vec2(u,v));}void main(){vec4 a=fr(uFrame);vec4 b=fr(uNext);float al=mix(a.a,b.a,uMix);vec3 c=(a.rgb*a.a*(1.0-uMix)+b.rgb*b.a*uMix)/max(al,0.001);gl_FragColor=vec4(c,al);}";
        const mat = new THREE.ShaderMaterial({
          uniforms: {
            map: { value: null },
            uFrame: { value: 0 },
            uNext: { value: 1 },
            uMix: { value: 0 },
            uTime: { value: 0 },
            uVel: { value: new THREE.Vector2() },
          },
          vertexShader: vs,
          fragmentShader: fs,
          transparent: true,
          depthTest: false,
          depthWrite: false,
        });
        const mesh = new THREE.Mesh(geo, mat);
        scene.add(mesh);
        let W = 1,
          H = 1,
          rects = [],
          bw = 300,
          bh = 290;
        let pos = { x: 0, y: 0 },
          prev = { x: 0, y: 0 },
          vel = { x: 0, y: 0 };
        let from = { x: 0, y: 0 },
          to = { x: 0, y: 0 },
          ctrl = { x: 0, y: 0 };
        let phase = "hover",
          phaseT = 0,
          dur = 1,
          hoverDur = 1.5,
          seqI = 0,
          facing = 1,
          facingT = 1,
          T = 0,
          dirty = true;
        const seq = [1, 3, 0, 2, 4, 3, 1, 0, 2, 4];
        function rnd(a, b) {
          return a + Math.random() * (b - a);
        }
        function clampPt(p) {
          const mx = bw * 0.45,
            my = bh * 0.4;
          p.x = Math.max(mx, Math.min(W - mx, p.x));
          p.y = Math.max(my + 50, Math.min(H * 0.7, p.y));
          return p;
        }
        function target(i) {
          const r = rects[i] || { x: W / 2, y: H / 2, w: 50, h: 50 };
          return clampPt({ x: r.x + r.w * rnd(0.4, 0.6), y: r.y + r.h * rnd(0.35, 0.55) });
        }
        function layout() {
          W = hero.clientWidth || window.innerWidth;
          H = hero.clientHeight || window.innerHeight;
          renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, P.maxDpr || 2));
          renderer.setSize(W, H, false);
          const cv = renderer.domElement;
          cv.style.width = "100%";
          cv.style.height = "100%";
          camera.right = W;
          camera.top = H;
          camera.updateProjectionMatrix();
          const hr = hero.getBoundingClientRect();
          rects = letters.map(function (el) {
            const r = el.getBoundingClientRect();
            return { x: r.left - hr.left, y: r.top - hr.top, w: r.width, h: r.height };
          });
          const portrait = W < H;
          bw = portrait ? Math.min(W * 0.62, 420) : Math.min(W * 0.26, H * 0.46);
          bh = (bw * 323) / 333;
          clampPt(pos);
          clampPt(to);
          dirty = true;
        }
        function startMove() {
          from = { x: pos.x, y: pos.y };
          const i = seq[seqI++ % seq.length];
          to = target(i);
          const dx = to.x - from.x,
            dy = to.y - from.y;
          const d = Math.sqrt(dx * dx + dy * dy);
          ctrl = {
            x: (from.x + to.x) / 2 + rnd(-0.12, 0.12) * W * 0.3 - dy * 0.18,
            y: (from.y + to.y) / 2 + rnd(-0.1, 0.1) * H * 0.3 + dx * 0.12,
          };
          clampPt(ctrl);
          dur = Math.max(0.7, Math.min(1.5, (d / Math.max(W, 1)) * 1.8 + 0.5));
          phase = "move";
          phaseT = 0;
          if (Math.abs(dx) > 40) facingT = dx > 0 ? -1 : 1;
        }
        function update(dt) {
          phaseT += dt;
          if (phase === "move") {
            const u = Math.min(1, phaseT / dur);
            const e = u < 0.5 ? 4 * u * u * u : 1 - Math.pow(-2 * u + 2, 3) / 2;
            const a = 1 - e;
            pos.x = a * a * from.x + 2 * a * e * ctrl.x + e * e * to.x;
            pos.y = a * a * from.y + 2 * a * e * ctrl.y + e * e * to.y;
            if (u >= 1) {
              phase = "hover";
              phaseT = 0;
              hoverDur = rnd(1.3, 2.3);
            }
          } else {
            pos.x = to.x + Math.sin(T * 1.7) * 6;
            pos.y = to.y + Math.sin(T * 2.3) * 5;
            if (phaseT > hoverDur) startMove();
          }
          const ivx = (pos.x - prev.x) / Math.max(dt, 0.001),
            ivy = (pos.y - prev.y) / Math.max(dt, 0.001);
          vel.x += (ivx - vel.x) * Math.min(1, dt * 8);
          vel.y += (ivy - vel.y) * Math.min(1, dt * 8);
          prev.x = pos.x;
          prev.y = pos.y;
          facing += (facingT - facing) * Math.min(1, dt * 10);
        }
        function render() {
          const fps = 14;
          const ft = T * fps;
          const f = Math.floor(ft);
          const m = ft - f;
          const sg = facing < 0 ? -1 : 1;
          mat.uniforms.uFrame.value = ((f % 8) + 8) % 8;
          mat.uniforms.uNext.value = (((f + 1) % 8) + 8) % 8;
          mat.uniforms.uMix.value = m * m * (3 - 2 * m);
          mat.uniforms.uTime.value = T;
          mat.uniforms.uVel.value.set(vel.x * (facing < 0 ? -1 : 1) * sg * sg, vel.y);
          mesh.position.set(pos.x, H - pos.y, 0);
          mesh.scale.set(bw * facing, bh, 1);
          const sp = Math.max(-0.3, Math.min(0.3, Math.abs(vel.x) * 0.0002));
          mesh.rotation.z =
            -sg * (0.14 + sp) +
            Math.max(-0.2, Math.min(0.2, vel.y * 0.0001)) * sg * 0.0 +
            -vel.y * 0.00008 * sg;
          renderer.render(scene, camera);
        }
        function still() {
          const r = rects[2];
          if (r) {
            pos.x = r.x + r.w * 0.5;
            pos.y = r.y + r.h * 0.4;
          } else {
            pos.x = W / 2;
            pos.y = H * 0.4;
          }
          clampPt(pos);
          prev.x = pos.x;
          prev.y = pos.y;
          to = { x: pos.x, y: pos.y };
          T = 0.12;
          vel.x = 0;
          vel.y = 0;
          render();
        }
        let last = performance.now();
        function loop(now) {
          requestAnimationFrame(loop);
          const dt = Math.min(0.05, Math.max(0, (now - last) / 1000));
          last = now;
          if (P.paused) return;
          if (P.reducedMotion) {
            if (dirty) {
              still();
              dirty = false;
            }
            return;
          }
          T += dt;
          update(dt);
          render();
        }
        const loader = new THREE.TextureLoader();
        loader.setCrossOrigin("anonymous");
        loader.load(
          "https://agents-dev.6to9.ai/visuals/3cc24f85de3cde3746f0651b4712f125.png",
          function (tex) {
            tex.minFilter = THREE.LinearFilter;
            tex.magFilter = THREE.LinearFilter;
            tex.generateMipmaps = false;
            mat.uniforms.map.value = tex;
            layout();
            pos = { x: W * 0.12, y: H * 0.3 };
            clampPt(pos);
            prev.x = pos.x;
            prev.y = pos.y;
            to = { x: pos.x, y: pos.y };
            seqI = 0;
            startMove();
            hero.classList.remove("nogl");
            window.addEventListener("resize", layout);
            if (document.fonts && document.fonts.ready) {
              document.fonts.ready.then(layout);
            }
            requestAnimationFrame(loop);
          },
          undefined,
          function () {
            hero.classList.add("nogl");
          },
        );
      }
    </script></body
  ></html
>

Made by 6to9. Back to all motion patterns.