My website stopped describing the lab and started showing it
Lesson from:
From hand-typed counters to a static site that reads about a hundred live figures from a one-person lab every 30 minutes
At 10:00 UTC this morning, a small script on my machine woke up, asked the lab about itself, and wrote down the answers. 72,312 works in the library. 817 published threat-intelligence reports. 168 running Kubernetes pods. 82,239 IP addresses classified by what they were trying to do to my sites. 1,496,725 vectors in the vector database. It saved all of it as one file of about a hundred figures, and my website read it.
Half an hour later it did it again. It will do it again while you read this.
In April 2026 the numbers on my site were typed by hand, and were wrong by the time I had saved the file. This is the story of how a static personal page turned into a live window into a one-person lab, and why that turned out to matter more than any design.
Contents · 11 sections
In one minute #
- My website, www.shuffle-on.com, is plain HTML served by nginx from my home lab. No framework, no database behind it.
- In April 2026 every figure on it was hand-typed: attack counts, library size, model counts. They went stale within hours.
- In May the threat pages started reading live data every 15 minutes. By June every number on the site came from one generated JSON file.
- On 14 October 2026 a full redesign went live: one design system, every page built around real, live figures from the lab.
- Ten days later it was shared on Hacker News. About 35 people came from there. The numbers were right for every one of them.
- The lesson: a portfolio that shows live evidence beats one that describes past work. And the hardest part is not the numbers; it is keeping them honest.
The before: a brochure for a machine that never sits still #
On 24 April 2026 I built a page called “Defense” for my site: 767 lines of hand-written HTML, eight sections, a kill-chain diagram, and 16 animated counters. The counters were animated and looked alive. Every number they showed was typed into the file by hand.
Two days later I added three new threat-intelligence reports, and the task notes list what I updated with them: the stats banner. 347,000 events, 3,282 attacking IPs, 51,667 bans, 37 days. By hand. The next day I built a “Knowledge” page, which I called The Vault, advertising 414 offline reference archives, 131,000 indexed documents, 54 AI models, 28,000 books. By hand. The same day I wrote “The Workshop”, a long page about my AI agent and its tools, because someone asked in a comment on LinkedIn how my indexing and search worked, and the answer needed a whole page.
If you have ever kept a CV or a portfolio, you know this problem from the other side. You describe what you did. The description is a snapshot. The work keeps moving, and the snapshot slowly becomes a polite lie.
My lab moves faster than most. The live numbers post opens with the moment that made it impossible to ignore: the count of running containers on my homepage read 141, 159 and 154 within a single hour. Any figure I typed was wrong before I had finished deploying it.
The first live page #
On 22 May I replaced the “Live Feed” page. Until then it showed a static snapshot of the alerts my honeypot had sent to my phone in April. The new version was rendered by the browser from a JSON file that a script regenerated every 15 minutes from the threat-intelligence database: recent attackers, countries, networks, credentials tried, commands run, campaigns. On its first day it showed 4,138 attackers, 112,000 attempts, 6,656 enriched IPs and 54 campaigns. The honeypot page got the same treatment the same day.
Three days later I shared it on LinkedIn. My notes from that Sunday are almost comically precise: 15 impressions after 30 minutes, 69 after two hours, 155 after eight. Two reactions. Two saves. Two link clicks. The assessment in my record reads: saves greater than reactions means people see value. Optimism is also a kind of data.
By June the whole site followed the same idea: one JSON file of public numbers, regenerated every 30 minutes, and a 51-line script that fills them into any element marked with a data-stat attribute. That post covers the mistakes too: a live value silently overridden by a constant for a month, an internal error message published to the internet, and AI-written copy that invented facts.
A name #
On 2 October the lab got a name on every page: LSN, Local Sovereign Network. It sounds like a small thing, and technically it was: a script stamped the mark into the footer of 12 share images and injected the same block into 834 pages, the hand-written ones and the hundreds of reports generated by the dossier pipeline, so they all said the same thing about where they came from.
It mattered because of what the site was turning into. A collection of pages written at different times, by me and by agents, in different moods and styles, became one body of work with one signature. A visitor landing on a report from a search engine now sees the same name as a visitor landing on the homepage, and can follow it back to the machine that produced both.
The quiet failure #
The most instructive day for the site was not a launch. It was 3 October, the day a test deleted my home folder. The scripts that generated the site’s numbers lived there. They were gone.
When I restored them and ran them exactly the way the scheduler does, with the scheduler’s minimal environment, I found two older bugs hiding underneath. The cluster command-line tool was not on the scheduler’s path, so every call to it had failed, and the site had been publishing ? namespaces and ? pods while the output went nowhere. And the honeypot summary took about 12 seconds to answer against a 10-second timeout.
A live number that fails silently is worse than a static one. The static one is at least honestly old. The scripts are now in Git, linked from where the scheduler expects them, and they publish only the type of an error, never its message.
The turning point: 14 October #
In October I rebuilt the whole site. The brief I gave was short: easy to scan, numbers automated, and keep every address that search engines and people had already linked to. A twelve-page redesign, from the homepage to the classroom page, with a single shared stylesheet and script so every page looks and behaves the same. It went live on 14 October, after four backups taken at different stages of the night.
What changed was not the colours. It was the stance. The old site described a lab. The new one is built around evidence from it:
- the homepage has a “lab status” panel fed by live figures;
- the library section shows the size of the library as it is right now, not as it was when I last remembered to update it;
- the threat-intelligence section links to more than 800 reports, each sealed so that a reader can verify it in the browser;
- the share images that appear when someone posts a link are rendered by a script from the same live numbers, so a link shared today shows today’s lab.
A week later I made one change that taught me something about what “live” is for. On 21 October I asked for the small counters under the descriptions of the newer services to be replaced, in my words, “with the intelligent facts behind these services”. A counter that says a young service has done 148 pages undersells it. A tile that says how it works, glossary first by a large model, draft and proofread by a smaller one, translation memory, explains it. Big live numbers stay where they are impressive: the library, the files, the entities. Small ones become facts.
The same day the homepage got a ten-second film, made overnight in the sandboxed 3D studio my agent drives, from one sentence of input. People watched it to the end, and then again: the average watch time on LinkedIn was longer than the film.
The technical heart: making live numbers trustworthy #
If you want a static site that tells the truth about a changing system, this is the whole mechanism, and none of it needs a framework.
One generator, one file. A scheduled script queries the real sources (the cluster, the library catalogue, the threat database, the search engine) and writes one JSON file:
{
"generated_at": "2026-11-05T10:00:02+00:00",
"library_works": 72312,
"dossier_published": 817,
"k8s_pods": 168,
"web_threats_ips": 82239
}
Write it atomically. Write to a temporary file, then rename it over the old one, so a reader never sees half a file. Serve it with Cache-Control: no-store.
Fallbacks in the HTML. Every number has a sensible value in the markup, so the page reads correctly without JavaScript, and to crawlers:
<b data-stat="library_works">72,304</b> works
A tiny binder. A few dozen lines of script fetch the JSON and replace the text of every data-stat element, with optional short formats like “1.5M”.
Errors as types, never messages. If a source fails, publish "error": "timeout", not the stack trace. Internal error messages contain things the public must not see.
Test under the real scheduler. Run the generator with the scheduler’s exact environment at least once. My ? pods existed for exactly this reason.
Constants are fallbacks, not overrides. When a hand-typed snapshot and a live value share a key, the live value must win. Check the merge order.
Who it is for, and what it is #
I started by thinking the site was a portfolio, a place to show what I build. It still is. But the live numbers changed what kind of portfolio it is.
A traditional portfolio asks you to trust the author’s description. Mine asks you to look. The library counter is not a claim about something I did once; it is the library, right now. The threat reports are not samples; they are the actual output of the actual pipeline, sealed. The analytics on the site follow a consent model I set up deliberately, which I wrote about in the consent-mode post, because a site that talks about sovereignty should not quietly track its visitors.
That openness has a cost. Every number on the site is a promise that the machine behind it is working. When a pipeline breaks, the site shows it.
On 24 October the site was submitted to Hacker News under the title “A sovereign AI lab on one machine in my apartment – zero cloud”. In a four-hour window around the launch the site received 2,749 requests from 1,532 addresses. When I separated people from machines, about 35 real visitors had come from Hacker News itself. Thirteen link previews in chat workspaces. Seven hundred and ten fetches of the share image from sites that mirror Hacker News. For one evening, the robots were far more interested in my apartment than the humans were.
It is a funny number and a humbling one, and it stays in the record exactly as it was. It is a good correction to the fantasy that a good page is enough. Distribution is its own problem, and I have not solved it.
What changed #
- Numbers went from typed by hand, to generated every 15 minutes for one page, to generated every 30 minutes for the whole site.
- The site went from describing the lab to being read from it.
- Failures became visible, and the generators became code under version control.
- Small numbers became explained facts; big numbers stayed live.
- The site went from a brochure to evidence: live counts, sealed reports, a book with a DOI.
Questions people ask me #
Can a static website show live data without a backend? #
Yes. Generate a JSON file on a schedule on the server, serve it like any other file, and let a few lines of JavaScript fill the numbers into the page. Keep fallback values in the HTML for crawlers and for readers without JavaScript. No API server and no database connection from the browser are needed.
How often should a portfolio site refresh its numbers? #
As often as the numbers meaningfully change, and no more. My threat pages refresh every 15 minutes because attacks are constant; the site-wide figures every 30 minutes. The share images use the same file, so a link shared today shows today’s figures.
Is it risky to publish live data from a home lab? #
It can be, so publish only aggregate public figures, never internal hostnames, addresses, error messages or anything that maps your network. Treat the generator like any other public endpoint: decide field by field what is allowed out, and publish the type of an error rather than its text.
Did Hacker News bring a lot of traffic? #
Not much human traffic: about 35 real visitors in a four-hour window around the launch, against thousands of automated fetches by previews, mirrors and bots. A front-page burst would have been easy to serve, since the site is static. The lesson was that a good page does not distribute itself.
Closing #
In April I typed 347,000 into an HTML file, and by any normal standard the page was finished. That was the problem.
What I have now is not a website about a lab. It is a small window cut into the side of one, in a 44 m² apartment in Iași, through which anyone can see the machine working, half an hour at a time. Most visitors are robots. A few are people. For both, the numbers are true.