The homepage used to have a background. It now has a render loop. A single <canvas> sits behind every page on dhseadev.online, and one number drives all of it: how far down the document you have scrolled, normalised to zero through one.
Nothing about that is new technology. There is no WebGL, no shader, no animation library, no framework. The whole engine is about twenty kilobytes of plain JavaScript drawing into a 2D context, mounted once in the theme header so it reaches every page rather than only the front one.
What the scroll actually drives
My first attempt keyed the animation to which section was on screen. That is the obvious design and it is wrong, because it turns a continuous gesture into a staircase: the scene jumps at boundaries and sits still in between.
The version that shipped reads one scalar instead. Every property of the scene is a small table of keyframes against that scalar, interpolated at draw time: where the prism sits, how wide it spreads, how many rays it throws, how far the light bends, how dense the pixel field gets. Eight scenes of copy scroll past, and the scene under them moves the whole way without a single discrete step.
Short pages have no scrollable range to read, so the arc falls back to a slow time drive. That case sounds like an edge case and is not: it is what a phone in landscape does to a page you thought was tall.
One number sets both the colour and the bend
The part I like most is almost free. Each ray carries a wavelength value, and that one value picks both its colour from the brand ramp and the angle it refracts at. Short wavelengths bend harder and land violet, long wavelengths bend less and land warm, exactly the way the physical thing works.
Because the coupling is real rather than decorative, the spectrum fans out on its own. I never had to place a colour. The eight-stop ramp is the site palette one chroma step up from the tokens used for text, so the light reads as brighter siblings of the interface rather than as a separate decorative system, which is the difference between a scene and a screensaver.
The pixel field picks up the same treatment. Five shapes, coloured from the same ramp, with particles shed from their edges. I had built a canvas for this site before, in Prism Break and again in the Prism Maker, and the lesson from both was that a scene needs to be doing one thing, not four.
The ship, the asteroids, and the black hole
The old ambient layer had a small ship that drifted around and occasionally shot a shard. It was charming and it was ambient, meaning it happened whether or not you were there.
The ship survived the rewrite with a plot. Partway down the page a black hole opens on the far side from the prism, the rays visibly bend toward it, and the ship flies harder, weaves the asteroid field, and pulls out of the well as the light reconverges near the end of the scroll. All of it is the same scalar. There is no timeline, no state machine, and nothing to get out of sync: scroll back up and the ship flies the escape in reverse, because there was never a sequence to rewind.
The footer had a hard line, and it was not the gradient
With the canvas behind everything, the footer grew a visible seam across the top edge. My first read was the obvious one: the footer gradient starts too abruptly.
Measuring said otherwise. The footer is a template part, and the theme gives it a stacking context of its own, so it paints over a fixed canvas underneath no matter what the canvas is doing. The gradient did need a transparent start, but the seam was structural. The fix also had to use pixel stops rather than percentages, because the footer is between five hundred and six hundred pixels tall depending on width, and a percentage stop that clears the links at one height buries them at another.
While it was open I rebuilt the footer content itself. It had become one tall stack of links; it is now four columns that collapse to two and then to one, with the widest column splitting internally so it stops towering over its neighbours. The block lost about ninety pixels of height without losing a single link.
Two rules that shipped and never painted
Both defects worth writing down were the same shape, and neither is visible in the code.
The footer grid rules were live, correct, and completely inert, because the container carries an inline display:flex attribute and an inline style beats every stylesheet. The new homepage background was also live, also correct, and also invisible, because the theme paints the body element over it. I reported that second one as shipped before I measured it, which is worse than the bug.
Presence in the served bytes proves a rule was delivered. It never proves the rule applies. The only evidence that counts is the computed value on the element that actually paints, read from the live page, before and after. That is now a written rule in my own tooling rather than a thing I remember, which is roughly the same lesson as the day every gate was green and the button did nothing, and the same reason a canvas can fail without failing.
What it cost
One canvas, one glow layer, one wordmark, eight scenes, and about twenty kilobytes of script with no dependencies. It honours reduced-motion preferences, it is marked hidden from assistive technology, it never takes a pointer event, and it turns itself off below the phone breakpoint where it would be scenery nobody asked to download.
Checked at ten widths from 1920 down to 320: no horizontal overflow anywhere, every footer link comfortably tappable, and every keyboard stop still showing a visible focus ring. The best evidence was not any of that, though. It was scrolling the thing slowly and watching the light bend.
Everything else is where it was: the projects index, the games index, the devlog, the full site index, the press kit with the brand marks, and the browser arcade. Only the light is new.
