InkRealm  Back to the scroll Home

Notes · InkRealm Studio · 9 October 2026

How this site is painted

Almost everything on the inkrealm.studio home page is drawn in your browser as you scroll: the mountains, the mist, the brush, the calligraphy and the seal. The one exception is a still frame of the same shader, shown while the renderer starts; the pictures in this note are screenshots of the live page. This note explains how each piece works.

Five ranges of grey ink-wash mountains on black, with mist between them and a faint moon.
The landscape: five ridge layers, mist between them, one moon.

A landscape made of columns

The painting is a single full-screen fragment shader (WebGL 2, through three.js). It stacks five ridge layers from far to near. Each layer is a row of individual peaks: the screen is divided into cells, every cell gets one mountain with a random position, width and height, and neighbouring mountains are merged with a soft maximum so their silhouettes flow into each other. A small octave of “rock” noise roughens the ridge line.

float peaks(float x, float cell, float seed) {
  float ci = floor(x / cell);
  float h = smax(0.0, 0.0, 0.06);
  for (int k = -1; k <= 1; k++) {
    float c  = ci + float(k);
    float cx = (c + 0.15 + 0.7 * hash11(c * 1.37 + seed)) * cell;
    float w  = cell * (0.30 + 0.62 * hash11(c * 2.71 + seed * 1.3));
    float H  = 0.07 + 0.93 * pow(hash11(c * 4.13 + seed * 0.7), 1.9);
    float dx = min(abs(x - cx) / w, 1.0);
    h = smax(h, H * pow(max(cos(dx * 1.5707), 0.0),
                        1.5 + 1.3 * hash11(c * 1.37 + seed)), 0.06);
  }
  return smax(h, 0.0, 0.06);
}

A ridge height depends only on the horizontal position, so it is the same all the way down a column of pixels. The renderer therefore computes every layer’s height and slope once per column, in a small pre-pass that is one pixel tall per layer, and the main shader looks them up. The slope decides which faces turn toward the moon and carry lighter ink. Below each ridge the wash fades toward the foot of the mountain, where a band of drifting mist sits in front of the next layer. Each layer pans at its own speed as you scroll, which is what makes the page feel like a handscroll being unrolled.

A brush that lifts the ink

Your pointer is a brush. Its strokes are kept in a small off-screen texture (up to 768 pixels on its long side) that is updated every frame in two passes. The first pass stamps the stroke: a capsule along the pointer’s path, thick when you move slowly and thin when you move fast, with gaps cut into it at speed for a dry-brush look. The second pass lets fresh ink creep a little along a fixed pattern of paper fibres, so edges feather instead of staying round. Every stroke also carries a “life” value that runs down over a few seconds; when it falls below a per-pixel threshold the stroke breaks up and the ink closes again.

Where the brush has lifted the ink, the shader shows a second picture of the same terrain: contour lines of every ridge, nodes connected along the middle ranges, and a slow rain of glyphs. The glyphs are drawn into a texture from the same stroke outlines used for the calligraphy, so no Chinese web font is downloaded.

The same mountains drawn as green contour lines on near-black, with connected nodes along the ridges and columns of falling glyphs.
The layer beneath the ink, fully revealed.

Calligraphy written stroke by stroke

The large characters are not a font. Their stroke outlines and stroke order come from the open Make Me a Hanzi data set. In SVG, each stroke’s outline becomes a clipping path, and a wide line drawn along the stroke’s centre is revealed by animating its dash offset, one stroke after another, in the order a calligrapher writes them. A turbulence filter roughens the edges so they read as ink rather than vector shapes. The red seal uses the same outlines cut out of a rounded square, with a noise mask that leaves the uneven gaps of a real stamp.

Ink that gathers into characters

In the “What we make” chapter, tens of thousands of particles are simulated on the GPU: their positions and velocities live in floating-point textures that update each frame. Each pillar has a character; its outline is rasterised once, target points are sampled inside it, and every particle is pulled toward its target while curl noise stirs it. When the target changes, the turbulence rises briefly, so the ink disperses and settles into the next character.

The whole landscape compressed into a long horizontal scroll, mounted in a dark frame with thin roller lines at each end.
The finale pulls back to show the whole scroll at once.

Keeping it light

Tools

We built this site with Claude Code, from the shaders to the page layout, and drafted this note with it from the site’s source. The open-source libraries and data it relies on are listed on the credits page.

About InkRealm Studio

InkRealm Studio is an independent game studio in Adelaide, Australia, making original games of many paths, built with AI and painted in ink. Our first title is in development. Home · hello@inkrealm.studio