Curated internet, served daily

A Reassurance Button With No Tracking, Rebuilt on My Homelab

A tiny page offers a button and a voice with no tracking and no data collected, so I rebuilt it on my homelab and found the voice is the whole product.

Research

When the build fails and the deadline looms, a small page offers a button. Press it and a voice speaks one sentence: ‘you are not alone in this’. Beside the button sit the terms: no tracking, no data collected. That is the whole offer.

I read it as a spec rather than a product. A button, a voice, an absence. The absence is the interesting part. Most pages I visit want something before they give anything: a cookie choice, an address, a script hosted somewhere I have never heard of. This page wants a press and returns a sentence.

I did not have the code behind it. I had its words, which is enough to rebuild the thing and enough to check the claim. The claim is testable in a browser with no extra tools.

Open the page with the developer tools network panel visible, press the button, and watch what leaves the machine. Outbound requests appear in that list. If the page talks to an analytics endpoint, it shows up there.

So my evening had a narrow shape. Rebuild the button on the homelab, keep it as small as I could, then hold my copy next to the original promise: no tracking, no data collected.

The easy parts, I assumed, were the HTML, the serving, and the styling. A button is a button. The hard parts were the voice and the honesty of the absence.

A voice means recorded audio or a synthesizer, and both carry a smell. Audio means a file, and a file means choosing a sentence and living with it.

I also wanted to know what a page like this costs to keep alive. A static page of this size needs no database, no session, no server logic, so hosting it costs close to nothing.

That is why rebuilding beats bookmarking. Something that asks for nothing is cheap enough to own outright. Owning it is the only way to keep its promise true after somebody else’s priorities change. Bookmarks rot. A file in my own directory does not.

Setup

I built it in a directory on the homelab and served it as static files.

mkdir reassurance
cd reassurance

The page is a plain HTML file: a button, an audio element, and a short script that plays the file when the button is pressed. No framework, no build step, no analytics snippet, no fonts pulled from elsewhere.

<button id=press>press</button>
<audio id=voice src=voice.mp3 preload=none></audio>
<script>
  const button = document.getElementById('press');
  const voice = document.getElementById('voice');
  button.addEventListener('click', () => voice.play());
</script>

For a look at it on the laptop I opened the file directly in the browser. To reach it from the phone on the same network, I served the directory:

python -m http.server

and opened the address it printed. Anything beyond the LAN goes through the reverse proxy I already run, which supplies the name and the certificate without adding infrastructure.

The audio came off the voice recorder on my phone. I read the sentence, exported the file, renamed it voice.mp3, and dropped it beside the HTML. I also tried the speech synthesis installed on the homelab, writing the same sentence to a file and swapping it in.

The recording ran long. I trimmed the front and the tail, listened to it twice, and left the second take in place. The directory holds the page, the recording, and nothing else.

Findings

The page loads from a file and the button plays the audio. With the network panel open, pressing the button produced a request for the audio file and nothing else: no analytics endpoint, no font host, no error reporter.

The promise held in my copy, and it held because I had added nothing that could break it.

The absence turned out to be the easy part. Nothing to configure, nothing to switch off, no consent banner to design, no retention policy to write. Privacy here is subtraction, and the work is refusing to add anything later.

Nothing on the page has to be maintained. There is no dependency to update, no endpoint to keep alive, no third party that can change its terms under me.

The phone behaved as well as the laptop. Because the sound starts from a tap, the autoplay rules that silence pages which begin playing on load never entered the picture. The button is the gesture and the trigger at the same moment.

Serving it was unremarkable. Static files, a proxy already running, no new process to watch. I restarted nothing, which is the nicest thing I can say about a deployment.

Then the voice, which is where the evening went. I recorded myself reading a sentence I had written and then read too often, and it sounded exactly like that. Flat. What makes the original land is harder to record than a sentence: a person is saying it.

Synthesis was worse. The local model pronounced everything correctly and carried no intent at all. It sounded like a lift announcing a floor. Useful for reading a menu aloud, wrong for this.

In a quiet room the recording landed. With a fan running behind me it sounded thinner, and the sentence had to carry what the audio could not.

Repetition is a real limit. The page says the same words each time, and by the time I had listened enough to judge my own recording, the sentence had stopped landing. I could rotate lines, but rotation means more recordings or a synthesis step. A synthesis step drags a server into a design that currently needs none.

The invisible cost sits below all of that. Nothing is collected, so I have no idea whether anyone presses the button. No counter, no log, no evidence that it helps. A counter is a line of code, and it would also be the end of the offer. I left it out and accepted the blindness.

Worth naming the temptations, since I felt all of them: a press counter, an uptime check calling in from outside, a prompt asking whether it helped, a weekly message. Each is defensible in a product review. Each turns a page that asks for nothing into one that asks for something.

There is a version with a server: generate the line on demand, rotate the sentences so they stay fresh. I sketched it and stopped. The moment the sentence is generated, the page needs compute, a process to keep alive, and a reason for anyone to care whether it is up. The static page needs a file.

I kept my own recording, flat as it is. The flatness does not matter; the person does. A stranger with a better microphone would have made a better file and a worse thing. The voice is the whole product.

The last thing I noticed: the page is small enough to hold in my head. By the end of the evening it read like a note left for whoever needs it. That shift is the real result of the experiment.

Boundaries

Nothing here proves anything about the original page’s traffic. I watched my own copy’s network panel, not theirs, and a promise stays a promise only until somebody checks it.

Nor does this say whether pressing a button helps anyone; feeling less alone is not something I can measure from a homelab. It also does not cover the working conditions that make the button feel necessary. A page can acknowledge a bad night. It cannot fix a bad quarter.

The test ran on my machines, on my network, in an evening: no browser matrix, no screen reader pass, no slow connection. And the result stops at the voice. Without somebody willing to be the voice, the page is a button that plays a file.

Verdict

Build it if the pieces exist: a directory, an HTML file, a recording of a voice, a proxy that is up anyway.

The version to skip is the one that answers back, counts presses, or learns from feedback; each of those needs the data this page promises not to keep. It also collapses the moment the sentence arrives with a wink.

The picture I keep: the end of a long Thursday, a terminal full of red, the tab open in front of me. A press, a sentence in a familiar voice, back to the error.

Record the sentence in your own voice and leave the page somewhere the next bad build will find it.

homelab self-hosting privacy

← Back to Deep Dives