Colophon
How this was made
Every choice behind this site, why it went that way rather than the obvious way, and what it would take to change my mind.
This site has no framework, no build step and no dependency. It is PHP files on a server with the content in JSON beside them, and that was a decision rather than a limitation. Most of what follows is a consequence of it.
Every constraint here was chosen for this particular brief, and the brief is unusual: one author, a laboratory rather than a product, a deployment that must never need a build machine standing between the source and the site, and a requirement that the thing running in production is the thing written in the editor. Change any of those and most of these answers change with them. Where a choice would be wrong somewhere else, it says so.
The cost sits on the other side of the ledger and is worth naming: no hot reload, no component library, nothing checking types. What catches a mistake here is reading it, or a smoke test that fetches every page and looks at how each one ends.
As of 8 October 2026 that comes to 62,189 lines of PHP across 160 files, 63,700 of JavaScript and 22,304 of CSS. About 148,193 lines in total, none of it compiled, minified or generated.
Why there is no database here
There is no database on this site, and that is not a view about databases. On anything heavier I reach for one early and without argument. Real volume, more than one writer, anything that needs a transaction or a join: that is where a relational store belongs, and it is where most of what I build in earnest begins.
It is the wrong answer here, and the reasons are specific enough that they would not survive being generalised. The content is a few hundred kilobytes and will never be more. There is exactly one writer. Nothing needs a transaction, a join or a query planner. Against that, a database would have added a service to keep alive, credentials to hold, a schema to migrate, and a backup story separate from the one the files already have. For a laboratory of this size, that is cost with nothing on the other side of the ledger.
So every project, lyric, photograph and message is a JSON file in a directory outside the web root, which buys two things at once: nothing in there can be fetched over the web, and a deploy can never overwrite something written in the panel.
Knowing which of the two situations you are actually in is most of the skill. Two things would flip it: a second writer, or anything that needs a transaction. Either one and it is a database, immediately. The rows I already have are the shape of a table, so the move is an afternoon rather than a rewrite. That is the real test of whether a simple choice was craft or laziness: whether you can say exactly what would change your mind, and what it will cost you on the day it does.
What replaces the things a database gives you
Writes are atomic. A file is written under a temporary name and renamed over the old one, so a save interrupted halfway leaves the previous version whole instead of half of each.
Every write also keeps what it replaced. 5 versions of each register sit in a shelf beside it, stamped to the millisecond. Atomic writing was never the real danger. The danger is a complete, correct save of the wrong content, which looks exactly like a good one until somebody notices what is missing. That shelf exists because of a day when something was missing.
There are 34 registers in all, each its own file, each independently restorable.
The editor is blocks, not a canvas
Pages are composed from blocks of known types rather than typed into a rich text area. A contenteditable surface hands you HTML written by a browser, which then has to be cleaned on the way in and trusted on the way out. Here the HTML is generated by the site, so there is nothing to sanitise and every block renders in the site's own components.
There are 9 types. Text takes four marks and no more: bold, italic, code and a link. Everything is escaped first, so nothing an author writes can become markup by accident.
This is the constraint I would defend least in general. For a team producing a hundred pages a week it is the wrong trade and a proper editor earns its complexity many times over. For one person writing carefully, it removes an entire category of problem and costs almost nothing.
Numbers that count themselves
A page can carry a token like the ones in the paragraph above, and it is replaced when the page renders. That mechanism exists because of a specific failure: this page used to claim the site was three hundred lines of PHP. It was true the day it was typed and wrong by a factor of fifty seven by the time anybody read it again.
A colophon whose only value is being accurate cannot afford a number that rots. So the numbers that rot are measured instead of written, and the source is counted once every six hours rather than on every visit. Every figure on this page works that way, the count above included, which was taken on 8 October 2026 and refreshes itself. If a number here is ever wrong, the code is wrong and not the writing.
The language is in the address
English sits on the ordinary addresses, Norwegian under /no/. That is not a matter of taste. An address should mean the same thing to everybody, always, and a language that lives in a cookie turns it into something that means different things to different people.
It has been measured that there is no shared cache in front of this site today. The rule stands anyway, as insurance against the day somebody puts one there.
The chooser falls both ways: with no Norwegian you get English, with no English you get Norwegian. It also reports which one answered, and that is the one thing that lets the panel list everything currently showing the wrong language with a link straight to the screen that edits it. A field with no suffix is read as the English column, so nothing written before the split was lost.
The front page is the only place anybody is looked at, and it sends no-store anyway because the picture rotates. If the browser asks for Norwegian and you have not chosen for yourself, you are sent on to /no/ with Vary and no-store on the redirect itself, because a cached 302 there would have sent the whole world to Norwegian. The signal is the browser setting and never an address: a Norwegian in Berlin and a Briton in Trondheim are both ordinary readers here, and the setting is right about both.
Some of it is English on purpose and should stay that way. The board prompted in English in Norway too, and so does the log from the generator.
Underneath, the prefix comes off in three lines and the fifteen rules below run unchanged on what is left; the last of them deliberately carries no [L], so the substitution is fed onward. The address still holds /no/ where PHP reads it, so the canonical comes out right without the head knowing that language exists at all. The rest is one chooser that falls both ways, and a record of which stored fields are bilingual, kept by hand.
Counting people without following them
There is no cookie and no consent notice, because there is nothing to consent to. A visitor is identified by a hash of address and browser mixed with a secret that is thrown away and replaced every midnight, so it can tell two people apart today and cannot connect anybody to yesterday.
What is kept is a daily count and which pages were read. Nothing that reaches back to a person. That is a real trade and not a free one. It means no returning visitors, no funnels, no cohorts, nothing that follows a person across a week. For a business that would be a serious loss and a proper analytics product would be the right call. Here it buys the ability to look at what people read without holding anything about who they are, which is worth more to me than the graphs I gave up.
Everything is gathered by a small beacon rather than counted while the page renders. That costs the reader with scripts off and buys three things: the public pages pay nothing for being measured, most crawlers drop out on their own, and the owner can switch himself off without the server needing to recognise him.
The contact form does the work, not you
No CAPTCHA. Instead your browser solves a small hash puzzle in the background while you type, and by the time you reach the button it is already done. For one message it is a fraction of a second nobody notices. For somebody sending ten thousand it is real processor time, over and over, which is the only kind of obstacle that scales the right way round.
A message is written down before the email about it is sent. Delivery is the flimsiest link in any contact form, and one that loses messages quietly is worse than a page with no form at all.
The fax around the contact form
The form above is untouched. It posts, the server redirects, and a refresh cannot send it twice. The fax puts seven seconds around the submission and then submits for real: the sheet feeds in, the line is dialled, and the message prints one scan line at a time.
It is the receipt, not the transport. Without JavaScript the button is a button, and the message arrives just the same.
The text is rastered to one bit on a canvas 1728 pixels wide, with a four by four ordered dither, which is the real speckle and not a soft edge. The sheet follows the words, because a fax rolled out what it needed, and a short letter on a full sheet of A4 reads as a fault on a screen.
Two numbers here are true about different things, and it is worth saying which is which. The performance takes seven seconds because somebody is watching it. The report gives the time a real Group 3 fax would have spent on exactly that many lines. A fax report that lies about the time is the one detail in a fax anybody would actually check.
Kvad Player, and how it follows the singing
The player has a name, which is an odd thing to give a part of a website and is deliberate here. Kvad is the old Norse word for a poem that is performed rather than read, and that is exactly what this shows: the kvad, while it is being sung.
It matters because work of this kind is assumed to have been fetched from somewhere. It was not. It was written for this site, it runs nowhere else, and what follows is everything that is behind it.
A song carries a cue for every line, and each cue has both an in and an out. Having both is what makes a pause a real pause: nothing stays lit through an instrumental break, and the column has already moved to the line that is coming, so a long gap reads as intended rather than as a stall.
The cues are typed by hand, in the panel, by holding a key while the song plays. Every other method tried first demanded that you stop the music to write down where you were, which is the one thing you cannot do while listening for a beat.
Where the time comes from
The obvious source of time is the audio element's own progress event. It is the wrong one: it fires roughly four times a second, which is fine for a progress bar and useless for a word that has to arrive on a beat. A quarter of a second late is not a rendering detail, it is a different word.
So the clock is read from the display instead. A loop synchronised to the screen's refresh asks the audio element what time it is on every frame, which on an ordinary monitor is sixty readings a second rather than four, and costs almost nothing because the work in each frame is a handful of comparisons.
Every loop is given a number when it starts, and checks that number before each frame. A loop whose number is no longer current returns instead of drawing. Without that, opening the player a second time leaves two loops running against the same element, both writing to the same nodes, and the result is a flicker nobody can reproduce on demand.
Which word is lit, and when
Each line is split so that every word is its own element. The first attempt clipped a bright copy of the line from the left, which works until a line wraps: a clip is geometric, so it cuts straight down the page and lights the same distance into every visual row at once. Words are inline boxes, they follow the wrap by themselves, and a word is what is actually being sung.
Where each word begins is stored once, at build time, as a fraction of the line. That fraction is weighted by letter count rather than divided evenly, because an even split gives a one letter word and a thirteen letter word the same slice of time, and the drift inside a single line is visible immediately. Letters are a crude stand-in for syllables and an extremely cheap one, and they land far closer than counting words does.
Each frame then works out how far through the line the music is, and counts words forward while their stored fraction has been passed. Counting rather than rounding matters more than it sounds: rounding to the nearest word lights it at its midpoint, which is half a word late, on every word of every line. That was the entire mystery behind an early complaint that the player felt sluggish.
Finally the whole thing is nudged ninety milliseconds early. A display that chases the voice reads as broken. One that leads it very slightly reads as singing. And only the words that changed since the previous frame are touched, so a frame in which nothing happened costs one comparison and no writes at all.
Fitting the type to the song
The type is sized to the song rather than to the screen. Before anything is shown, every line is measured at its natural width, the widths are sorted, and the one at the eighty second percentile is taken as the width the song actually needs. Sizing to the longest line instead would shrink an entire piece to accommodate one unusual line, and the few genuine outliers are tightened individually afterwards.
Two smaller corrections sit on top. The line being sung is drawn slightly larger than the rest, so the width available is the column minus that growth, or the emphasis clips the last word off the one line the reader is looking at. And a final percent is held back as slack, because a line measured to exactly the space available still wraps often enough to matter.
None of it reaches out for help. There is no lyric service, no third party and no network call anywhere in it. The timings are typed by hand in the panel, one line at a time, and the whole thing runs on what is already on the page.
Commentary that knows when to appear
A song can carry timed notes in the margin, each with a picture and a caption, in either language, appearing and leaving on cue. The listener chooses how much of the screen they take.
The notes are marked by holding a key while the song plays, which turned out to be the only editing method that survived contact with actual use. Everything else demanded that you stop the music to write down where you were.
The page can be read aloud
Every piece of continuous text here can be read aloud. The server holds no key and calls no service: the audio was made once, elsewhere, and sits here as files. So it works with the modem line up and during the power cut, and it costs nothing per play.
The filename is a fingerprint of what is actually going to be said, worked out by the same code the page uses when it draws the text. The fingerprint is taken of the speakable text and not of the markup, so markup can move without retiring the audio. Had the generator held its own opinion about what to read, the two would never have met and the button would never have appeared. Change a word and the page points at an address that does not exist, and the button goes with it. That is the right way to fail: better no voice than a voice reading last month.
The paragraph being read is marked in the margin, and a click jumps there. The marks are bound to the text by its words rather than by counting. Counting assumes the extract and the template walk the body in the same order, and the day a module is drawn but not read, every mark after it is wrong and nothing says so. Matching on words fails one paragraph at a time, visibly.
It is made one call per paragraph and joined into a single file, which is what gives a cue per paragraph instead of one for the whole piece. That is also what makes it possible to jump into a reading at all.
The gallery does its arithmetic early
Photographs are laid out in justified rows, and the row breaks are worked out on the server rather than in the browser. Because every picture's dimensions are known before anything is sent, the rows are fixed in the markup and the page cannot reflow as the images arrive. Galleries that do this after loading are the ones you watch shuffle into place.
The spherical photographs are wrapped back around the viewer by a shader that works out, for every pixel on screen, which direction the eye is looking and which part of the frame lies that way. No sphere, no triangles, no library, about a hundred and twenty lines.
Nothing reads the position stored in a photograph. Not stripped later: never read.
Axes, not themes
The look is 4 independent axes: the design, the palette, the layout, and whatever is drifting behind the text. None of them knows about the others, so every combination is valid and there are 875 of them.
Packaged themes would have locked colour to type, and then it would never have been possible to discover that one combination beats both of the complete proposals it was assembled from. Which is the entire argument for building it this way, and it only paid off once, which was enough.
A period of the year can take the whole thing over. A named date range carries its own pictures, its own words over them and its own look, and the narrowest matching period wins, so Christmas beats winter without either needing to know about the other.
The band over the title takes the picture's own proportions up to 660 pixels of height, and crops past that. A plate of words floating in the middle of a great deal of empty sky looked worse than a missing edge.
The picture at the top is chosen by the sun and the weather
The picture above the title is neither random nor a list of seasons. A rule says when it applies, and a variant inside it says under which sky. Rows are the sun, columns are the weather, and a cell is a picture. The shape is a grid because an empty cell is then visible: with a list you can only see what exists, and whether there is anything for a rainy night becomes a question you answer by counting.
A rule that cannot draw anything is not in force. If Christmas holds only a snow picture and there is no snow, Christmas steps aside and the ladder carries on down to the floor. The alternative is a blank band on the one day of the year you were looking forward to.
The draw between several candidates is seeded by the day and never random, so everybody opening the site on the same day sees the same thing, and the bench in the panel tells the truth about a date instead of tossing a fresh coin every time somebody looks at it.
The sun and the weather belong to the place the pictures come from, never to the visitor. It is a fact about Melhus, not about you. The sun is worked out in degrees above that place rather than read off a clock: at 63 degrees north the highest it gets at midwinter is 3.27 degrees, which is inside the band called the edge, and midsummer midnight is below the horizon. A clock would have called the first one day and the second one night. The weather comes from the meteorological institute by way of the scheduled job, and counts as unknown once it is over three hours old, so a rule that needs weather never fires on a guess.
The late hour, and the sun over Melhus
After dark the front page is drawn dark. The sun over Melhus decides that, worked out for 63.29 degrees north with a function PHP already carries. Sunset here falls just after half past two at midwinter and just before half past eleven at midsummer, so the window swings by nine hours across the year, and the shortest day is four and a half hours long against twenty and a half in June.
The visitor's own clock was the first idea. Nothing in a web request says what time it is where the reader is sitting, and every way of guessing at it is worse than the last. There is a second problem underneath that one. At this latitude a line reading it is dark where you are would be false to most of the country on a summer evening, and a sentence that is wrong once is not believed again. The sun over one village can be calculated to the minute, and it asks the reader for nothing.
The line above the title carries it in both directions. After dark it greets the town and gives the hour the light comes back. In daylight it gives the hour the light goes, and when the day runs under six hours or over nineteen it gives the length of the day instead, because by then the length is the more interesting number.
Under the dark the whole page is veiled, and an unlit candle stands beside the note in the margin with one line asking you to light it. Pressing it lifts the veil and the page comes up. Blowing it out puts the night back. The small cross underneath takes the offer away and does not make it again.
Lighting is remembered for eight hours, which is one night. The first version remembered it for good, and the effect was to make the late hour happen exactly once per browser: every night after the first was an ordinary page, and nobody would ever have known what they were missing. A candle burns down, so the memory of it burns down with it.
Anybody who has pressed Daylight is left alone, because a site that puts the lights out over somebody's stated preference has stopped being helpful and started being clever. While the sun is the one deciding, the theme button in the header says Follow the light rather than naming a theme, since at that point no theme has been chosen and the darkness on screen is not a setting.
The note in the margin keeps its heading and changes what it says underneath, once per lighting, six times. The sixth stays for good. Rotation of that kind is the difference between a joke and wallpaper: it is a reward for coming back rather than a thing that happens at you.
With a mouse there is a second light, and it follows the pointer. It is a hole cut in the veil's own mask. Laying a bright patch over a drape only lifts the colour of the drape and gives you grey; taking the drape away shows the page at full strength, and that is the whole difference between a page that looks lit and a page that is. A warm disc rides over the hole and carries nothing but the colour of the flame.
It writes once per animation frame however fast the mouse is moved, and it is skipped entirely where there is no real pointer. A phone has nothing to carry a light with, and a glow parked in the corner of a touch screen would be worse than no glow at all.
A checker that will not cry wolf
The links register asks each place about once a day whether it is still there, 12 at a time, and says nothing until one has failed 4 days running. Even then it only says so. Nothing is ever removed.
The verdict has three values rather than two, and the third one is the important one. Only two things count as gone: nothing answered at that name or address, or something answered and said the page is not there. A certificate that cannot be verified, a redirect loop, a server being short with a robot: those are facts about this end or about the weather, and they get no vote.
That third value exists because of a failure during construction. On a machine with no certificate bundle, every address in the register came back unreachable, and a checker allowed to draw a conclusion from that would have declared the whole register dead inside four days. An alarm that goes off when nothing is wrong is one people learn to ignore, and an alarm people ignore is worse than none at all, because now everybody believes they are covered.
Private means absent
Half the register can be marked private. A private link is not hidden with a stylesheet and not folded behind a switch. It is not written into the page at all for anybody who is not signed in.
Hiding a thing and not sending it are two different guarantees, and only one of them survives somebody opening the source. Any page that varies by who is asking also refuses to be cached, because a copy holding the private half must never be handed on to the next visitor from an edge cache.
A notebook the server cannot read
Behind the panel there is a notebook for the things worth carrying: server notes, licence keys, recovery codes, the occasional password. The server stores it as one lump of ciphertext and has no way to read it.
The key is made of two halves that live apart. One is a PIN of four to six digits, which never leaves the browser. The other is a secret in the data directory, handed out only to a request that has already proved it is signed in. Everything readable is put together in the browser and sealed again before it goes back, so a stolen backup, a stolen disk or a curious host gets noise.
Four million rounds of stretching sit between the PIN and the key, and that number was measured rather than copied. The six hundred thousand everybody uses took sixty four milliseconds here, and sixty four milliseconds against a million possible PINs is a weekend of grinding on one core. Four million is about four hundred milliseconds, which is a fair price to pay once per unlock.
None of it changes the ceiling, and the page that opens it says so out loud. Anybody who takes the running server holds the other half of the key, and a short PIN cannot survive that. It is a notebook and not a vault, and the name is the warning. Bank, email recovery, the domain registrar, anything that unlocks everything else: that belongs in an audited manager with an app of its own, and none of it is in here.
Leikr, and what is built to go wrong
Type leikr anywhere on the site, or open the visit counter in the footer, and a screen appears with everything on this site that is built to break on purpose. The word is Old Norse for a game. Each row is an act: something that happens to the page, in the page's own machinery, with a way out. Nothing is on until you press it, and the acts refuse each other: whichever one holds the page says so, and the others wait. The two below are the ones with a door of their own on the front page, beside the counter.
The generator
Press Off-grid and the mains fail. What follows is what a plant room would do, in its own vocabulary: the UPS takes the load and says so under the counter, with a charge that drains and a runtime that shrinks; the transfer switch times out the mains and sends the start signal; the standby set cranks, and the page's light dips with every turn of the starter because the starter is fed from the same battery; it cranks again, weaker, and again, and locks itself out. The battery reaches low, the alarm goes from every ten seconds to every second, the digits flicker, and the page goes dark. Then a candle is put in front of you, unlit, and the set is yours.
The dark is the same mask the front page uses after sunset, with the hole where the flame is, so the page is read by the light you carry. The set is one drawing, and everything on it takes the hand the way the real thing does: the cap comes off with three quarters of a turn; the fuel can, which stands somewhere else, pours only while its spout is held at the neck, and what misses the neck drips and pools on the floor under it, where the candle was put down; a choke that a cold engine will not start without and a warm one will not run with; a recoil cord that is a real chain of points on a spring and wants four pulls; a cable drum with a voltmeter, and a plug dragged across the floor on a cable that lies where it falls and can be trodden on. Once it runs, two levers trim the frequency and the volts, and the page tells you how you are doing: the wrong frequency rolls a band over it and puts a hum under it that goes with the hertz, the wrong volts dim it, and the readout above the set goes green when both are right. Hold that for ten seconds and the mains come back.
There are two ways to fail. Carry the flame to the petrol and the neck glows red for two seconds before the set is in pieces on the floor. Push the volts past two hundred and fifty and the meter on the drum sparks for three before the site feed is fried. Each ends in a card with what happened and two buttons, and the log under the counter keeps the whole thing, with the time.
Every sound is made on the page: the dial tone of a UPS alarm, the starter, the cough, the engine that follows the governor and dips when the load comes on, the pour, the cap, the match, the blast. They go through a short concrete room and are placed where the thing stands, left to right. Nothing is fetched, and the panel can replace any of them with a recording. The hint button writes what to do next on the dark and makes the part it names glow three times; the messages are written on that dark by a hand, revealed the way a pen reveals them, and every line lives in the panel in both languages.
Underneath it there is no library at all. It is a state machine with three ways in and two ways out; a Verlet chain used twice, once as the cord on a recoil starter and once as a cable with gravity and a floor; a hole in a mask image that follows a dragged element; a script engine for the transfer, editable without a deploy; WebAudio synthesis for a dozen sounds with a convolver room and per-source panning; and a canvas hand for the writing. All of it is keyed on prefers-reduced-motion, so somebody who asked for less movement still gets the whole game with the parts that move on their own standing still.
The modem line
The other badge by the counter reads 56k. A card asks first, because it makes a noise. Say yes and the screen goes green on black and a terminal dials: ATZ, the number, a dial tone, the digits, one ring, and then the handshake, which is a public-domain recording where the panel has one and otherwise built on the audio graph the way the real one was built on the line: the answer tone with its modulation, the two ends introducing themselves, a comb of probing tones across the band, and the training noise that ends in the silence that means connected. The speed is negotiated on each call, so it is not the same one twice.
From there every page arrives the way pages did, inside a browser from 1998: grey chrome with Back, Forward, Reload and Stop, an address bar, a throbber that turns while anything is still coming, and a status bar underneath that says what it is doing. The page is measured in bytes and released at the rate the line gave, block by block in the order a browser would have drawn them: text clipped from the top, photographs in four interlaced passes the way a GIF arrived, other pictures sharpening in five, and on the photography page pictures as text, drawn from the real pixels one character per cell and weighing what they look like they weigh. Around it the period puts itself back: a marquee, the visit counter in LEDs, 88 by 31 badges, a webring, Best viewed in 800×600, and a status line that starts scrolling once the document is done. Each of those is a switch in the panel.
A bar at the bottom counts kilobytes, seconds, a speed that wanders, and tellerskritt, at the cheap rate after five and at weekends. Now and then the modems retrain, sometimes down a step for good. Sometimes the line is engaged on the first dial. Sometimes somebody in the other room picks up the phone, and there is a Redial. Ten idle minutes and the provider hangs up; an hour and the session is over. Hang up says what a modem says, tells you what the call cost, and puts the broadband back. The connection follows you from page to page for the session, and nothing else is remembered.
What holds it up: a stamp written into the head before the first paint, so a page that is going to arrive slowly does not first arrive all at once; a progressive reveal driven by measured bytes rather than by timers; canvas sampling of same-origin images into text; the whole page stripped back with all: revert and dressed again as 1998; a scripted terminal whose stages make sound; and the player's own audio taken apart and put back through the modem's. It is the same door, card and panel pattern as the generator, which is why the next thing with a door costs a morning.
The Eagle BBS
In 1992 there was a board of this name on the PC in my room, on PCBoard, with one modem on the family's one telephone line, and this is it, back to serve the site. The card on the 56k badge tells the story and dials the number it had. The number is busy more often than not, and the dialling box counts down to the next try, because one line meant one caller. When it rings, the handshake is heard and, under the box, explained: the answer tone, the two tones finding each other's timing, the rasp of the line being measured, the silence before CONNECT.
From there the whole page is a monitor: eighty columns by twenty-five rows in the very pixels a VGA card drew, drawn to a canvas cell by cell and scaled as pixels so the block letters meet, with the sixteen colours and the terminal program's status line underneath. Everything arrives through a queue at the speed the modems agreed on, so a menu takes a second at 14400 and six at 2400, and Space skips it. One key does one thing. The bulletins are the site's own texts; the projects are files, listed with eight-letter names and fetched with Zmodem before they can be read; the photographs are viewed as characters; the songs travel as MIDI and play on a sound card you may not have, or as one square-wave voice on the PC speaker while the words draw. There is private mail that waits on the board for the one it is for, a wall for one line each, a list of last callers, a door with dice, a sysop who answers once, and forty-five minutes a day, counted down on the status line and enforced.
Under it is a character-cell terminal with a draw queue paced by the negotiated line speed, the site read as text by one endpoint so the board follows the site, a V.32bis handshake and a PC speaker built on the audio graph, and General MIDI played without samples by webaudio-tinysynth (MIT), whose top line is taken for the speaker. The IBM VGA 8×16 face comes from VileR's Ultimate Oldschool PC Font Pack (CC BY-SA 4.0), which is why the screen looks like the screen. The same grid is written to an element off the screen as plain text, so a screen reader is given the board as words rather than a picture of words, and on a touch screen the keys the prompt will accept are drawn as buttons.
The first time a browser opens the front page, the page is not there yet. What is there is this site's own history, played on the machines it happened on, about nineteen seconds of it, and it plays once per browser and never again, silently. A thin line at the foot shows where you are, with the years marked where the eras change, and Skip intro does what it says; so does any key, click or touch.
A tube comes on. It is 1992 and The Eagle BBS answers on one line: ATZ, ATDT 22106221, a wait while the modem dials and the two ends find each other at 2400 bits per second, CONNECT, the year in block digits, the board's name in block letters beside the eagle, PCBoard's questions, a password that echoes as dots, and the conference SAGA. Then five years pass in the same tube: the number is 22108057 now, the line is ISDN through a LASAT Unique 1280mi, and the board is Worldgroup on two lines, white and cyan on blue, with a Who's on box, a teleconference where two people talk, FidoNet mail at four in the morning, and a note that the old number went back to being the family phone. The carrier drops and the tube collapses to a line and a dot.
The dot is the throbber of a browser in 1998. It dials 22108057 again, verifies a user name and password while a 56k modem sings, and connects at 49333. The page is grey and set in Times, with a banner arriving in four interlaced passes, a marquee, a hit counter, the year as a picture of its own, and the picture of the day in 256 colours. The camera goes into that picture, the picture streaks into hyperspace, and out of the flash comes the photograph as it is, the year as it is, and the page underneath with the plate hung on its nails.
From the badge marked Intro by the counter, or the line over the welcome, it plays again with the modems audible: the dial tone and the digits, V.22bis finding its carriers in 1992, a click and two tones for ISDN in 1997, and the whole V.34 ceremony for the 56k call in 1998, answer tone, bong, probing comb, training and the digital hiss. The card beside it says what all of this was, in words, with the picture of the day timed at each stop's speed.
The journey is a page now as well, and that is a different thing from the intro. A film that plays once per browser is the wrong place for something you should be able to return to, and it cannot be searched, read aloud or linked to. So the same years stand on a page of their own: nine stops from 1991 to today, each dressed in the material of its own decade, with the doors into the machines standing where they belong.
All of it runs on one WebGL shader with two eras in it: a cathode ray tube with curved glass, scanlines, phosphor and flicker, and a web page in 256 colours with an ordered dither. On top of that sits an eighty by twenty-five text grid painted in the VGA face, four calls synthesised on the audio graph, and a hyperspace jump done as radial samples with procedural star streaks. The version is part of the key, so a reworked journey plays again, once, for everybody who saw the old one.
A card counts six things this page does when it thinks nobody is looking: the generator, the oil lamp, the call point, the board of 1992, the board of 1997 and the diskette. Only one of them has a mark to find. The call point hangs low in the margin beside the welcome, drawn as the real thing with its cover down, in the page's own ink until somebody presses it and the housing floods red. The other four are found by using them: strike a match and light the lamp when it is late, throw the main switch on the network page and watch for something to take the load, dial 22106221 or 22108057 from the switchboard at the foot of any page, switch on the machine the diskette sits in.
Three earlier stabs were homages, and they are off the count as of 14 September 2026: a foot from above, three rings to hold, a low pass over the page, in the spirit of Monty Python's Flying Circus, Battlefield and Top Gun. What was borrowed from each was an idea. Nothing from the originals was ever shipped: no music, no logo, no character, no still, no line of dialogue. The one borrowed image was not theirs either, Cupid's foot from Bronzino's Venus, Cupid, Folly and Time, painted about 1545 and long in the public domain, which is where the circus found it too.
None of the six is a demo. The oil lamp has nine notches on its key and a fire at the end of them. The generator is started on a pull cord, its governor drifting, feeding the page down a cable you can trip over. The call point is drawn as the object, its moulded words set as real SVG text so they hold in either language. The two boards run the same terminal, 1992 as eighty columns of VGA text painted to a canvas and 1997 with a window manager over the same thing. The diskette sits in a machine that boots, and its file system is data rather than code. Every sound is noise and sine waves shaped in the Web Audio API, except the 1992 handshake, which is a public-domain recording: two machines negotiating over a bad line is not something an oscillator can fake.
Every line the reader meets, in both languages, is edited in the panel.
The seal
Two winged, dragon-headed beasts flanking a sword that is also a cross, over a disc cut with runes, with two smaller crosses hanging under it. It stands whole once at the foot of every page, and its parts stand where the page already sets a mark: the ring beside the visitor counter, the wing feathers clasping the rule above the footer, the beast's head as the quiet door to the panel.
It is drawn rather than placed. A picture of it would have needed one file for daylight and another for firelight, four sizes for four screens, and it would still have been soft on a phone; a line is sharp everywhere and costs three kilobytes. Everything is cut once, in one file, and put where it is needed by transform, so the head on the ward and the head on the seal cannot come apart.
The ring is not decoration pretending to be writing. It says ROGER JOHANNESEN in Elder Futhark, one mark to a letter, with a lozenge where the space is, and the run is turned half a slot so that the blade passes through the disc in a gap rather than over a rune. Six of the letters are the same drawings the projects already use for their marks; five had to be cut for this and deliberately stayed out of that list, because the play mode counts the named runes and would rather stop than mislabel something.
The sword has a hollow centre, the way a fuller is ground into a blade, so the channel is real geometry and not a stripe. Put a hand on the seal and a charge falls down it and bursts at the point into forked lightning, shaped the way a real bolt is shaped by the air it happens to find: split the line, push the middle sideways, and do it again four times over. It never fires on its own.
And it is not a background. A dense symmetrical mark behind running text makes the letters restless at any opacity, and at five per cent it stops being an emblem and becomes stationery. The one place it lies behind the page is as a living background somebody chose, cut once into the ground and standing still, where the night's torch and the outage's candle find it: both of those covers are masks with a hole in them rather than washes laid on top, so the carving is already stronger inside the hole, and where the light passes the line takes it and turns amber.
The voices
Three things on this site are answered by a language model, and none of them is a chat in a corner. Page the sysop on The Eagle BBS and the owner answers as he was in 1992, from his room, at a typist's pace with the odd wrong key, knowing nothing after that year; when you ask about something on the site he looks in the file area, which is the site's own search drawn as dots, and he goes to bed when the board's clock or his patience runs out. Ring an extension on the switchboard, the word at the foot of every page, and the furniture of the front page picks up: the paraffin lamp after sunset, the generator in log lines, the counter that has seen every visit since 1998, the draugr from the dice door, both beasts on the seal at once, the weather over Melhus from MET's own numbers, and the skald, who only speaks in verse. Zero and the old number is an outside line to the board.
The skald also works alone. Once a morning, from cron and never in a page view, he is given what the site knows about the day, the date, the sun in degrees and hours, the forecast, the picture over the front page and the count at the foot of it, and cuts four to six lines into the margin under the note on over-engineering, in English and in Norwegian as two poems on the same facts. The lines are hatched by the stylesheet like the sum further down, burn in one after another when the reader reaches them, and stand over a line of plain data, which is the joke.
The model is Claude, spoken to with one request shape over curl, the way the weather is fetched. Every character is a card of instructions written here and editable in the panel, the rules under every card say that it is a program and must say so when asked, and nothing it says can change anything on the site. Until a key is placed in the panel every line answers with the fixed words it had before any of this existed, and when the day's allowance is spent every line is busy.
It is a model used as a character and never as an assistant. The persona block is cached with the day's facts after it. There are two tools and both are read-only: the site's own search, and a receiver to put down. A verse comes back as structured output in two languages at once. A refusal is handled as a line going quiet. The budget is counted per exchange, per caller and per day, and every one of those limits is told in character. The key sits outside the web root and never reaches a page.
The board of 1997
In 1997 the house got ISDN and The Eagle got Worldgroup. Dial 22108057 (from the switchboard, from the list of other boards on the 1992 board, or from the card on Leikr) and the line connects in a second with nothing to hear: a click and two tones, then CONNECT 64000. The screen is blue with cyan frames, as Worldgroup drew it. Your account from 1992 is carried over, and at login you choose the ANSI terminal or Worldgroup Manager, the Windows client.
What 1997 had that 1992 did not is other people. The teleconference on channel SAGA has regulars, characters a language model plays, several in one call, and the screen moves when they type. Two of them wrote letters on the old board. /who, /p and /go work from any prompt. Who's on lists four lines; the registry says who the users are; the forums carry FidoNet echo tags; the library comes down at 64000, which is a file in seconds where 1992 took the evening; e-mail goes out to the Internet on the night run; the Internet menu has telnet that refuses, Usenet with the blog as a newsgroup, and WebCast, which hands the line to the web of 1998. Behind the dice door of 1992 there is a hall now, narrated a turn at a time by the same draugr. The sysop is five years older and can be paged into the same split-screen chat.
Worldgroup Manager is the same board as windows: a toolbar, a teleconference window with the channel's user list, forums and the library as listviews, a download dialog with a progress bar at the speed of the line, pictures that arrive as pictures instead of seventy-eight columns of punctuation, and a status bar with the clock. The chrome is drawn in the stylesheet and the icons by hand, as a period costume; nothing of it leaks into the site. Every word the caller meets, in both languages, and every regular's card is edited in the panel.
Both boards run off one terminal engine, five years apart. ISDN is modelled as signalling rather than as song. Several voices arrive in one structured call, at their own pace, while the caller is still typing, and any one of them can be paged privately. The text adventure is narrated a turn at a time. The Windows client is drawn in CSS with sixteen-colour icons cut by hand, running the same content as windows and dialogs. And there are three bridges: the 1992 board dials 1997, 1997 dials the web of 1998, and the switchboard rings both.
The consumer unit in the rack
The network page is a list of real infrastructure documenting redundancy it has never had to use. So it was given the thing that stands upstream of all of it: the board, with the ways live.
The ways mean something, and the rules are the building's. Take the network way out and the standby path wakes and carries the traffic, which is the failover the topology note at the foot of that page has promised in words since it was written. Take that out too and everything in there is running while nothing can reach it, and those are two different words on that page. Take the server way out and every service follows its host, because the page already knows which service stands on which machine: the dependency is read rather than written down twice.
The main switch is under a lock, and the lock is why there is no dialog box there. The way you stop somebody throwing a main switch by accident is not a modal, it is a lock with a tag on it. Reading the tag is the question, taking the lock off is the consent, and the whole thing is correct procedure instead of a costume of one.
Nothing is remembered between visits. Switching a way off is a physical model and not a setting, and a page that remembered you had switched off the servers would be a page telling a lie about a real rack.
The devices are real buttons, so they take a keyboard and say what they are to a screen reader, and the state lives in one attribute rather than in a parallel model that can drift from what is on screen. A switch reads as a position and not as a colour: down is off on every board ever made. The body unfolds on a grid row going from nothing to one, rather than on a height somebody has to measure in a script and get wrong the first time a word grows.
The diskette
There is a diskette on this site, and its label has been written on twice. The top line is crossed out. What it says now is DO NOT DELETE, and that is the whole clue: it tells you something was deleted.
The way on is five real DOS skills, in the order somebody actually learned them: that DIR does not show everything, that TYPE reads a file, that a directory can lie, that deleted is not gone, and that the machine refuses to write because there is a tab on the diskette itself. Then you have to click the diskette in the drive and slide the tab across. It is the only place on this site where you have to touch an object to get any further.
The clock is the BIOS's and not yours. It stands at March 1992, just after the last file was written, and it runs while you sit there. Before that, DATE answered from the reader's own machine, which put the year 2026 on a 386 and gave the whole thing away in one command.
The file system is data and not code: one row per file with the attribute, whether the contents are binary, and the letter DOS threw away when the file was deleted. A new folder needs no JavaScript. The screen is a pre and not a canvas, because DOS printed immediately: there is no queue to model and no typing speed to fake, and a pre gives selectable text and a screen reader for nothing.
The panel behind the site opens with the answer: eight steps, and what each one asks you to know. Whoever sets a puzzle is the only person who cannot look it up later.
My old man's 486
In December 1994 my father bought a 486, and I requisitioned it more or less at once. It stands in the museum now, and it comes apart. Take the lid off and the board is underneath, drawn from above with the back edge at the top, which is how you looked at one. Every card can be unscrewed and lifted out, every jumper moved, every memory module unclipped, the processor lifted from its socket, the ribbon cables pulled off, the plugs on the front panel taken off, the standoffs under the board unscrewed. Then you put it back together and switch it on.
Anything that can be fitted wrong can be fitted wrong here, and what comes back is a symptom and never a message. A processor fed five volts where it wants three and a third stops being a processor, once, for good, and the machine says nothing about it, because the thing that would have said something is the thing that is gone. A video card told it has twice the memory it has repeats the top half of the picture across the bottom. A ribbon cable turned around lights the drive lamp and leaves it lit. A lead on the front panel put on backwards is a diode with the current the wrong way round, which is not a fault condition, it is nothing at all. Seventeen of these are laid out as a menu, ranked by how far the cause sits from the symptom, and the menu says only what the machine does.
Which is why nothing goes in without the instrument that finds it. A fault you cannot reach from inside the machine is a wall and not a puzzle, so the memory map, the interrupt table, the meter with its three settings, the beep codes on the sticker inside the lid, the booklet that came with the board and the service diskette were every one of them built because something needed them. The machine counts nothing on your behalf. You count the sockets. You count the pins along a header the board marks at one end only. You read the two lines printed on the processor after taking the heatsink off, because the heatsink is what is in the way.
Three voices say different things about the same object, and they are kept apart on purpose. The object carries its mark: the silk screen on the board, the colours on the leads, the corner cut off the processor. The paper carries the rule, printed and therefore in English on both sides of this site, because a manual for a no-name board screwed together in Taiwan was in English and nobody translated it. And a label on the wall carries the opinion, because a maker's own sheet can never say that its jumper table was a nightmare. The sheet is the nightmare.
Everything in it is data. The board is a table of parts and positions, so a second board is a row and not a drawing; the booklet on the shelf is worked out from the same constants the machine runs on, so a jumper added to the model turns up in the manual without anybody writing it down twice; and the memory arithmetic that decides whether a game will start is one function, read by the machine and by the service diskette alike. Around five hundred checks in the language test hold it together: a place that loses nothing when it is taken out is refused, a fault with no instrument to reveal it is refused, and a Norwegian word in the English column is refused.
And when it starts, it runs real games, which are the only things in the museum somebody else made. There are three in the box, and the sizes say something before a word is read: Commander Keen from December 1990 at 364 kB, Wolfenstein 3D from December 1992 at 1.3 MB, and DOOM from February 1995 at five and a half, which did not go on a diskette at all and came as two. Keen is licensed for distribution by Apogee, and Apogee's own VENDOR.DOC is on the diskette, where it says in print that everyone is encouraged to copy it and pass it around. The other two are id Software's shareware episodes, given away and asked to be passed on, and DOOM's installer still carries id's own two lines: SHAREWARE VERSION, PLEASE DISTRIBUTE. All three are here unaltered. Underneath them sits DOSBox, compiled to WebAssembly by js-dos, which is free software under version 2 of the GNU General Public Licence, and the source for all of it is published by the projects themselves. Nothing else here is borrowed. The board and its parts, the booklet, the diskettes, the notebook, the printer, the faults and every sound the machine makes were drawn, written and built on the page.
What the host hides
Error display is off on this server and cannot be turned on. A fatal error therefore produces a page that simply stops in the middle, with status 200 and no message anywhere. That cost this site once: the projects register showed one entry out of seven while every check reported green.
So every crash is now written to a log, leaves a marker in the HTML for the smoke test to find, and prints in full at the foot of the page for whoever is carrying the sign in cookie. Which is error display, for one person, on a host that does not offer it.
There is a health page that says plainly what is broken, and it exists for the reason a workplace keeps incident reports. The failure nobody hears about is the one that happens again.
The readout, and the page that asks itself
At the foot of every page stands a line with the place, the clock, the height of the sun in degrees, the sky, and which banner rule is in force this minute. The site worked all of that out before and kept it to itself.
It is empty markup in the page and filled afterwards, and that is not decoration. A number written into the HTML is true for whoever generated the page and a lie to everybody the edge hands that copy to, and a clock in a cached page is the clearest version of that mistake. With no script there is no readout at all, which is right: it is an instrument, not content.
The endpoint that fills it sends no-store, because nearly every page here is cacheable and several send no Cache-Control at all. The clock ticks from the server's own time and offset rather than the reader's, because the point of it is the place the pictures come from. One fetch a minute, and none at all in a tab nobody is looking at.
Once an hour the site fetches its own addresses the way any reader would, and writes down what it got back. The check that pays for itself is not the status code, it is that the answer ends with its own closing tag. Twice in one week this site went out with a missing file, and with error display off on the server every page came back as 200, with the right content type, and stopped in the middle of a sentence. Nothing watching status codes would have caught either time.
And age is a state. The check runs on the machine it asks about, so a machine that is off cannot tell anybody that it is off. If the snapshot is more than three hours old the site answers unknown rather than well, and the status page says so in as many words. A green light with no timestamp beside it is the one thing that page must never be.
One scheduled job, on purpose
Almost nothing here runs on a timer. The daily fold of the visitor log happens when the dashboard is opened, not at three in the morning, because a job nobody watches can stop running for a month before anybody notices.
The link checker is the single exception, since asking other people's servers whether they exist takes seconds and cannot hang off the front of somebody's page load. It runs from one small entry point, guarded so that it will not answer a stranger, and the spacing lives in the checker rather than in the schedule. Call it every hour and each link is still asked about once a day. Call it a hundred times by hand and it does nothing a hundred times.
If the schedule is never set up, nothing breaks. The links simply go unasked.
Search with no index
The search reads the whole store and looks, on every query. There is no index, no inverted list and nothing to keep in step with the content.
For a corpus of a few hundred kilobytes, which is less than one of the photographs, building machinery would cost more than it saves and would introduce the classic failure of an index quietly disagreeing with the thing it indexes. If this ever stops being fast it will be because the site got interesting, which is a good problem and a solved one.
The index that counts itself
The index is everything in one list, and it exists so the menu can stay short. That is why the list is kept by hand on purpose. A loop over the menu would look tidier and be wrong: a section taken out of the menu should stay here.
What stops the two drifting apart is six checks that fail if a menu item has no row. Verified by removing one and watching the test go off.
The figures are counted as the page is drawn, using the same expression the section's own page uses, so the index can never disagree with the page it points at. A number written into a sentence goes wrong the day something is added, and that has happened four times on this site already.
A number landing straight in front of a noun also forces the noun to agree with it, so the sentences are written the other way round, with the noun first: true at none, at one and at ninety.
Under the floor
PHP 8.3 behind nginx, hosted in Norway. No Node, no package manager, nothing generated at deploy time. The files that run are the files that were written, and a deploy is those files copied up. Pictures are resized on upload, stripped of the camera's location data and written as progressive JPEG in five widths.
Set in Young Serif, Literata and IBM Plex Mono, or whichever three the switcher is currently set to. Backups are taken by hand from the panel and are not yet scheduled, which is the most honest sentence on this page.