A lighthouse in the fog

Scriby is a word processor for writing books, and its website is a long scroll through one dark, foggy night. Behind every section, a lighthouse stands near the right-hand edge, its beam turning slowly through drifting fog, and its light grows a little as you read further down. Everything you see in it is one fragment shader, about a hundred lines, with no libraries.

0%
The real shader from gl.js. The slider stands in for the page's scroll position and runs the site's own timeline for glow, fog and beam. Move the pointer over the fog and it leans away a touch.

One shader, top to bottom

The fog is five octaves of value noise, domain-warped (the noise is sampled at coordinates bent by more noise), so it rolls and folds instead of scrolling past like wallpaper. The lighthouse is built from signed distances to a handful of boxes: a shaft that tapers towards the top, a gallery with a rail, a glass lantern room with two bars and a crossbar, a pointed roof and a finial. Every edge gets about a pixel of anti-aliasing from a smoothstep, and the base dissolves into the fog bank instead of meeting the bottom of the screen.

The beam took the most thought. A real lighthouse beam turns; it doesn't sway. So the beam makes one slow turn about every sixteen seconds, and it's brightest when it swings round to face into the screen and fades as it turns away. It's a bright core with a soft scatter, and it's stronger where the fog is thicker, because light needs something to hit. The lantern goes on after the vignette so it's always the brightest thing on the page, and a whisper of noise is added at the end to stop the dark gradients banding.

The page itself only ever sets three numbers. A scroll timeline nudges glow, fog and beam, and for most of the read they barely move, then in the last few percent the glow jumps from 0.22 to 0.78 and the fog thins, so you arrive at the end and the lamp is properly lit.

Being kind to slow machines

A full-screen shader is a lovely way to make an old laptop's fan spin up. So it renders at 80% of the CSS size to begin with (it's fog; nobody can tell), and a small governor watches the frame times. After 40 warm-up frames, every frame slower than 45ms adds a point and every quick one takes one away. Forty points and it drops the resolution to 60%, then 45%, and if it's still struggling it stops animating altogether and only redraws when one of the three numbers changes. Reduced motion gets that still frame from the start, with the beam held facing you.

The Pretend I'm a slow laptop button above is a demo addition: it fakes slow frame times so you can watch the governor step down. The readout in the corner shows the scale it has settled on.

Fog in squares

Then it turned out that on some Windows machines the fog was a grid of soft squares, like a JPEG at the wrong quality. On Windows, Chrome runs WebGL through ANGLE on top of Direct3D, and some of those GPUs were losing precision inside the noise hash. The old hash fed big numbers through big multipliers, so the value for one lattice cell no longer matched what its neighbour computed for the shared corner, and the seams showed.

float hash(vec2 p){
  p = mod(p, 61.);   // small, bounded inputs, whatever the time
  vec3 p3 = fract(vec3(p.xyx) * .1031);
  p3 += dot(p3, p3.yzx + 33.33);
  return fract((p3.x + p3.y) * p3.z);
}

The fix is the mod(p, 61.). The fog drifts with time, so the lattice coordinates grow for as long as the page is open; wrapping them every 61 cells keeps every input to the hash small, and the hash itself has no large multipliers left to lose digits in. The wrap is far too big to notice in fog. The same change added a guard: if the GPU reports no high-precision floats at all, the canvas hides and you get the flat night colour, which is better than squares.

Four rooms and three Enters

The rest of the site is pinned scroll scenes. Two of my favourites show the app's writing page. Scriby has four page themes, called rooms, and the site crossfades a sample page through all four as you scroll.

The Salt Road

Chapter 1 · The Lamp at Hollow Quay

Mara Voss had kept the lamp at Hollow Quay for eleven winters, and in all that time nothing had come out of the fog that she had not first heard coming.

That morning the fog sat on the water like a held breath.

Night
The four rooms, with the site's colours. Each room is five CSS variables: room, sheet, ink, dim ink and the room's own text colour.

Then there's the one feature I explain to everyone. In Scriby there's no menu for a scene break or a chapter. You press Enter once for a new paragraph, twice for a *** scene break, and three times for a new chapter, which numbers itself.

Press return.

Return (or Enter, with the page focused) once, twice, three times. Write a line (or any letter) types the next sentence. A simplified model of the editor's rule, not the editor.

The rule in the editor is pleasingly dumb: a second Enter on an empty paragraph turns it into ***, and a third Enter on the empty paragraph under a *** removes the break and splits the chapter there. ⌘Z puts it all back.

Built
October 2026
Stack
WebGL 1, GSAP ScrollTrigger, Lenis
Lives at
scriby.thomasalex.in
Source
Private

Built with Claude Code. The site sets its headings in a display face; this page uses Georgia and Newsreader instead. The sample prose is from the site.