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.