When you open yureka.dev you see my name and a DNA helix made of particles. Scroll down and those same particles fly over and form a globe. Keep going and they turn into the About scene, then an @ for Contact, then a running fox at the bottom. Scroll once more and you're back at the start.

All of it runs on YParticles, a WebGL particle engine I wrote for this site. This post walks through how the pieces fit together.

Every scene starts as a picture

For each section I paint a canvas the size of the screen. Sometimes it's text, sometimes an image like the globe or the fox, sometimes a scatter of dots. The engine looks at that canvas and decides where each particle should sit and what colour it should be.

My favourite part is that transparency doubles as density. Where the paint is fully opaque, the particles pack into a solid shape, like the letters of my name. Where it's faint, you get a thin dust. So most of the look comes from how I paint the image, before any physics runs.

The scenes also know what screen they're on. On a wide monitor the picture sits to the right of the text. On a phone it moves down so the text has room at the top.

Scrolling only moves a number

The home page never actually scrolls. It's a single screen, and the mouse wheel, touch and arrow keys all nudge one number. 0 is Home, 1 is Work, 2 is About, and so on. A spring eases the value on screen toward that number, so every move starts and stops softly instead of snapping.

When the number passes the halfway point between two sections, the engine gets the next shape and every particle flies from where it is to its spot in the new one. The particles never leave the screen. The helix turns into the globe right where it is.

Which particle goes to which spot is worked out with optimal transport. Each particle gets a short trip, so the whole cloud shifts together instead of tangling. Right after the page loads, a background worker prepares every scene and the moves between neighbouring ones. By the time you scroll, a switch takes a few milliseconds.

The number has no upper or lower limit. I take it modulo 5 to pick the section, which is why the page loops in both directions. The menu links set the same number, so clicking About and scrolling to About use the exact same transition.

The text is still real HTML

Every heading, paragraph and link on the page is normal HTML. Even my name at the top is a real h1. The renderer measures where each letter sits on screen and paints the particles in the same place. You see particles, but search engines and screen readers still get text.

Sections you can't see are marked inert, so pressing Tab never lands on a hidden link. The URL follows the current section too, which means a link like /#about opens the site on About. If your browser has no WebGL, my name shows up as plain text instead.

Keeping it smooth

The dots in each scene are painted in a worker, away from the main thread, so scrolling stays responsive while they're drawn. Text is the exception. It has to be drawn on the main thread, because that's where the web fonts are loaded.

Phones get 100,000 particles and bigger screens get 220,000. The cut-off is a screen whose shorter side is under 600 pixels. The smoke behind everything is painted once when the page loads. After that it only moves with CSS transforms, so the background never has to be redrawn.

The fox at the bottom has electric arcs crawling over its fur. The arc pattern only changes 14 times a second, so the engine now works it out once per change and reuses it for the frames in between. On my desktop that cut the cost of the arcs by about 70 percent. The saving is bigger on 120 Hz phone screens, which draw twice as many frames for every change.

Why the posts have no particles

The particle page is fine for a short introduction, but it's a bad place to read. An article needs text that stays still and links that work the normal way. So these posts keep the colours, fonts and smoky background of the home page and leave the particles out. If you miss them, the home page is one click away.