Live demo, running in your browser

Ink koi

A jade pond with giant ink-black CALM lettering where a koi swims a slow figure-eight, leaving fading ripples, with drifting bokeh particles and mouse parallax.

Assets

Code: MIT license. Imagery generated with Google Gemini · Video generated with Google Veo

<!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>Calm - ink koi</title>
    <style>
      :root {
        --jade: #bfe3d2;
        --ink: #27332f;
        --vermilion: #e2452b;
      }
      * {
        box-sizing: border-box;
      }
      html,
      body {
        margin: 0;
        height: 100%;
        overflow: hidden;
        background: var(--jade);
        color: var(--ink);
        font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
      }
      .hero {
        position: relative;
        width: 100vw;
        height: 100vh;
        height: 100dvh;
        overflow: hidden;
      }
      #stage {
        position: absolute;
        inset: 0;
        width: 100%;
        height: 100%;
        display: block;
      }
      .sr-only {
        position: absolute;
        width: 1px;
        height: 1px;
        overflow: hidden;
        clip: rect(0 0 0 0);
        white-space: nowrap;
      }
      .nav {
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 26px clamp(20px, 4vw, 56px);
        z-index: 2;
      }
      .brand {
        font-weight: 800;
        letter-spacing: 0.04em;
        font-size: 18px;
        display: flex;
        align-items: center;
        gap: 8px;
      }
      .brand i {
        width: 10px;
        height: 10px;
        border-radius: 50%;
        background: var(--vermilion);
        display: inline-block;
      }
      .nav nav {
        display: flex;
        gap: 24px;
        font-size: 14px;
      }
      .nav a {
        color: var(--ink);
        text-decoration: none;
        opacity: 0.75;
      }
      .foot {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        display: flex;
        justify-content: space-between;
        align-items: flex-end;
        gap: 24px;
        padding: 0 clamp(20px, 4vw, 56px) 40px;
        z-index: 2;
      }
      .foot p {
        margin: 0;
        max-width: 26ch;
        font-size: clamp(15px, 1.3vw, 19px);
        line-height: 1.4;
        font-weight: 500;
      }
      .button {
        display: inline-block;
        background: var(--ink);
        color: var(--jade);
        text-decoration: none;
        font-weight: 700;
        font-size: 15px;
        padding: 15px 28px;
        border-radius: 999px;
        white-space: nowrap;
      }
      .fb {
        display: none;
        position: absolute;
        inset: 0;
        place-items: center;
        background: radial-gradient(circle at 70% 60%, rgba(255, 255, 255, 0.35), transparent 45%),
          var(--jade);
      }
      .fb span {
        font-weight: 900;
        font-size: 27vw;
        letter-spacing: -0.04em;
        color: var(--ink);
      }
      .fb em {
        position: absolute;
        width: 9vw;
        height: 4vw;
        border-radius: 50%;
        background: var(--vermilion);
        left: 52%;
        top: 52%;
        opacity: 0.9;
      }
      body.fallback .fb {
        display: grid;
      }
      @media (max-width: 700px) {
        .nav nav {
          display: none;
        }
        .foot {
          flex-direction: column;
          align-items: flex-start;
          gap: 18px;
          padding-bottom: 32px;
        }
        .fb span {
          font-size: 46vw;
          line-height: 0.8;
          max-width: 2ch;
          text-align: center;
        }
      }
    </style>
  </head>
  <body>
    <main class="hero">
      <h1 class="sr-only">Calm</h1>
      <canvas id="stage" aria-hidden="true"></canvas>
      <div class="fb" aria-hidden="true"><span>CALM</span><em></em></div>
      <header class="nav">
        <div class="brand"><i></i>mizu</div>
        <nav>
          <a href="#">Practice</a>
          <a href="#">Journal</a>
          <a href="#">Sounds</a>
        </nav>
      </header>
      <section class="foot">
        <p>A breathing space for slower, quieter days.</p>
        <a class="button" href="#">Begin slowly</a>
      </section>
    </main>
    <script>
      (function () {
        var P = window.__pattern || { paused: false, reducedMotion: false, maxDpr: 2 };
        var canvas = document.getElementById("stage");
        var ctx = null;
        try {
          ctx = canvas.getContext("2d");
        } catch (e) {
          ctx = null;
        }
        if (!ctx) {
          document.body.classList.add("fallback");
          return;
        }

        var TAU = Math.PI * 2;
        var OMEGA = TAU / 32;
        var LIFE = 9;
        var JADE = "#bfe3d2";
        var INK = "#27332f";
        var FRAMES = 50;
        var CELL_W = 480;
        var CELL_H = 212;

        var W = 0;
        var H = 0;
        var dpr = 1;
        var unit = 1;
        var fs = 100;
        var glyphs = [];
        var portrait = false;
        var pcx = 0;
        var pcy = 0;
        var pax = 0;
        var pay = 0;
        var koiLen = 400;
        var par = { x: 0, y: 0, tx: 0, ty: 0 };
        var stillDrawn = false;

        var atlas = new Image();
        var atlasReady = false;
        atlas.crossOrigin = "anonymous";
        atlas.onload = function () {
          atlasReady = true;
          stillDrawn = false;
        };
        atlas.src = "https://agents-dev.6to9.ai/visuals/979d978b32fbf55b4d3f68f111f4fc77.webp";

        var koiCanvas = document.createElement("canvas");
        koiCanvas.width = CELL_W;
        koiCanvas.height = CELL_H;
        var kctx = koiCanvas.getContext("2d");

        function makeSprite(rgb) {
          var c = document.createElement("canvas");
          c.width = 128;
          c.height = 128;
          var g = c.getContext("2d");
          var grad = g.createRadialGradient(64, 64, 0, 64, 64, 64);
          grad.addColorStop(0, "rgba(" + rgb + ",0.45)");
          grad.addColorStop(0.7, "rgba(" + rgb + ",0.6)");
          grad.addColorStop(0.88, "rgba(" + rgb + ",0.95)");
          grad.addColorStop(1, "rgba(" + rgb + ",0)");
          g.fillStyle = grad;
          g.fillRect(0, 0, 128, 128);
          return c;
        }
        var spriteLight = makeSprite("255,255,255");
        var spriteDark = makeSprite("30,52,45");

        function mod1(v) {
          return ((v % 1) + 1) % 1;
        }

        var particles = [];
        function seedParticles() {
          particles.length = 0;
          var n = portrait ? 46 : 84;
          var s = 7;
          function rnd() {
            s = (s * 16807) % 2147483647;
            return s / 2147483647;
          }
          for (var i = 0; i < n; i++) {
            var z = rnd();
            particles.push({
              x: rnd(),
              y: rnd(),
              z: z,
              r: z < 0.5 ? 1.5 + rnd() * 4.5 : 12 + rnd() * 40,
              a: z < 0.5 ? 0.25 + rnd() * 0.35 : 0.06 + rnd() * 0.1,
              vy: 0.004 + rnd() * 0.01 * (0.5 + z),
              sway: rnd() * TAU,
              dark: rnd() < 0.3,
            });
          }
        }

        function typeFont(size) {
          return "900 " + size + "px 'Arial Black', 'Helvetica Neue', Helvetica, Arial, sans-serif";
        }

        function layoutType() {
          portrait = W / H < 0.8;
          var lines = portrait ? ["CA", "LM"] : ["CALM"];
          var track = -0.04;
          var topReserve = portrait ? 70 : 80;
          var bottomReserve = portrait ? 200 : 140;
          var availH = H - topReserve - bottomReserve;
          ctx.font = typeFont(100);
          var widths = lines.map(function (line) {
            var w = 0;
            for (var i = 0; i < line.length; i++) {
              w += ctx.measureText(line[i]).width + track * 100;
            }
            return w - track * 100;
          });
          var maxW = Math.max.apply(null, widths);
          var fsW = (100 * (W * 0.92)) / maxW;
          var capK = 0.72;
          var pitchK = 0.8;
          var fsH = (availH / (capK + (lines.length - 1) * pitchK)) * 0.96;
          fs = Math.min(fsW, fsH);
          ctx.font = typeFont(fs);
          var capH = fs * capK;
          var pitch = fs * pitchK;
          var blockH = capH + (lines.length - 1) * pitch;
          var firstBase = topReserve + (availH - blockH) / 2 + capH;
          glyphs = [];
          lines.forEach(function (line, li) {
            var lw = 0;
            var adv = [];
            for (var i = 0; i < line.length; i++) {
              var a = ctx.measureText(line[i]).width + track * fs;
              adv.push(a);
              lw += a;
            }
            lw -= track * fs;
            var x = (W - lw) / 2;
            for (var k = 0; k < line.length; k++) {
              glyphs.push({ ch: line[k], x: x, y: firstBase + li * pitch, front: line[k] === "A" });
              x += adv[k];
            }
          });
          pcx = W / 2;
          pcy = firstBase - capH / 2 + (blockH - capH) / 2;
          if (portrait) {
            pax = W * 0.27;
            pay = Math.min(H * 0.2, blockH * 0.42);
            koiLen = W * 0.62;
          } else {
            pax = W * 0.3;
            pay = H * 0.13;
            koiLen = Math.min(W * 0.3, 540);
          }
          unit = Math.max(0.6, Math.min(W, H) / 700);
        }

        function resize() {
          W = window.innerWidth;
          H = window.innerHeight;
          dpr = Math.max(1, Math.min(window.devicePixelRatio || 1, P.maxDpr || 2));
          canvas.width = Math.round(W * dpr);
          canvas.height = Math.round(H * dpr);
          ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
          layoutType();
          seedParticles();
          stillDrawn = false;
        }

        function pathAt(t) {
          var w = OMEGA * t;
          return {
            x: pcx + pax * Math.sin(w),
            y: pcy + pay * Math.sin(2 * w),
            a: Math.atan2(2 * pay * Math.cos(2 * w), pax * Math.cos(w)),
          };
        }

        function drawGhost(time) {
          var gx = -par.x * 14 + Math.sin(time * 0.11) * 26;
          var gy = -par.y * 8 + Math.cos(time * 0.09) * 14;
          ctx.save();
          ctx.translate(W / 2 + gx, H / 2 + gy);
          ctx.scale(1.2, 1.2);
          ctx.translate(-W / 2, -H / 2);
          ctx.font = typeFont(fs);
          ctx.textBaseline = "alphabetic";
          ctx.fillStyle = "rgba(255,255,255,0.16)";
          ctx.strokeStyle = "rgba(39,51,47,0.16)";
          ctx.lineWidth = 1.6;
          glyphs.forEach(function (g) {
            ctx.fillText(g.ch, g.x, g.y);
            ctx.strokeText(g.ch, g.x, g.y);
          });
          ctx.restore();
        }

        function drawType(time, onlyFront) {
          var mx = par.x * 20 + Math.sin(time * 0.17) * 10;
          var my = par.y * 10 + Math.cos(time * 0.13) * 6;
          ctx.save();
          ctx.translate(mx, my);
          ctx.font = typeFont(fs);
          ctx.textBaseline = "alphabetic";
          ctx.fillStyle = INK;
          glyphs.forEach(function (g) {
            if (onlyFront && !g.front) {
              return;
            }
            ctx.fillText(g.ch, g.x, g.y);
          });
          ctx.restore();
        }

        function drawParticles(time, near) {
          for (var i = 0; i < particles.length; i++) {
            var p = particles[i];
            if (p.z >= 0.5 !== near) {
              continue;
            }
            var r = p.r;
            var x =
              mod1(p.x + Math.sin(time * 0.15 + p.sway) * 0.02) * (W + 4 * r) -
              2 * r +
              par.x * p.z * 60;
            var y = mod1(p.y - time * p.vy) * (H + 4 * r) - 2 * r + par.y * p.z * 30;
            ctx.globalAlpha = p.a * (0.7 + 0.3 * Math.sin(time * 0.6 + p.sway));
            ctx.drawImage(p.dark ? spriteDark : spriteLight, x - r, y - r, r * 2, r * 2);
          }
          ctx.globalAlpha = 1;
        }

        function drawRipples(time, light) {
          var kc = Math.floor((OMEGA * time - Math.PI / 2) / Math.PI);
          for (var k = kc - 1; k <= kc; k++) {
            if (k < 0) {
              continue;
            }
            var tk = (Math.PI / 2 + k * Math.PI) / OMEGA;
            var age = time - tk;
            if (age < 0 || age > LIFE) {
              continue;
            }
            var c = pathAt(tk);
            for (var j = 0; j < 3; j++) {
              var a = age - j * 0.9;
              if (a <= 0 || a > LIFE - 2) {
                continue;
              }
              var u = a / (LIFE - 2);
              var radius = (14 + a * 42) * unit;
              var alpha = (1 - u) * (1 - u) * (light ? 0.5 : 0.32);
              ctx.beginPath();
              ctx.ellipse(c.x + par.x * 34, c.y + par.y * 18, radius, radius * 0.82, 0, 0, TAU);
              ctx.lineWidth = (2.4 - 1.4 * u) * unit;
              ctx.strokeStyle = light
                ? "rgba(255,255,255," + alpha + ")"
                : "rgba(30,60,50," + alpha + ")";
              ctx.stroke();
            }
          }
        }

        function drawKoi(time) {
          if (!atlasReady) {
            return;
          }
          var fp = (time * 24) % FRAMES;
          var i = Math.floor(fp);
          var frac = fp - i;
          var j = (i + 1) % FRAMES;
          kctx.clearRect(0, 0, CELL_W, CELL_H);
          kctx.globalAlpha = 1;
          kctx.drawImage(
            atlas,
            (i % 5) * CELL_W,
            Math.floor(i / 5) * CELL_H,
            CELL_W,
            CELL_H,
            0,
            0,
            CELL_W,
            CELL_H,
          );
          kctx.globalAlpha = frac;
          kctx.drawImage(
            atlas,
            (j % 5) * CELL_W,
            Math.floor(j / 5) * CELL_H,
            CELL_W,
            CELL_H,
            0,
            0,
            CELL_W,
            CELL_H,
          );
          kctx.globalAlpha = 1;
          var pos = pathAt(time);
          var s = koiLen / CELL_W;
          ctx.save();
          ctx.shadowColor = "rgba(15,45,35,0.32)";
          ctx.shadowBlur = 22 * dpr;
          ctx.shadowOffsetX = 14 * dpr;
          ctx.shadowOffsetY = 22 * dpr;
          ctx.translate(pos.x + par.x * 34, pos.y + par.y * 18);
          ctx.rotate(pos.a);
          ctx.scale(s, s);
          ctx.drawImage(koiCanvas, -CELL_W / 2, -CELL_H / 2);
          ctx.restore();
        }

        function render(time) {
          ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
          ctx.globalAlpha = 1;
          ctx.fillStyle = JADE;
          ctx.fillRect(0, 0, W, H);
          drawGhost(time);
          drawParticles(time, false);
          drawRipples(time, false);
          drawType(time, false);
          drawRipples(time, true);
          drawKoi(time);
          drawType(time, true);
          drawParticles(time, true);
        }

        window.addEventListener("pointermove", function (e) {
          par.tx = (e.clientX / Math.max(1, W)) * 2 - 1;
          par.ty = (e.clientY / Math.max(1, H)) * 2 - 1;
        });
        window.addEventListener("resize", resize);

        resize();

        var last = performance.now();
        var sim = 6;
        var STILL_TIME = 9;

        function frame(now) {
          requestAnimationFrame(frame);
          var dt = Math.min(0.05, (now - last) / 1000);
          last = now;
          if (P.paused) {
            return;
          }
          if (P.reducedMotion) {
            if (!stillDrawn) {
              par.x = 0;
              par.y = 0;
              render(STILL_TIME);
              stillDrawn = true;
            }
            return;
          }
          sim += dt;
          par.x += (par.tx - par.x) * 0.05;
          par.y += (par.ty - par.y) * 0.05;
          render(sim);
        }
        requestAnimationFrame(frame);
      })();
    </script>
  </body>
</html>

Made by 6to9. Back to all motion patterns.