Plutonic Rainbows

The Comparative Baseline Nobody Saved

There's a particular kind of knowledge that disappears not because it's wrong but because the conditions that produced it no longer exist. The pre-internet world — and I mean the actual texture of daily life before always-on connectivity — is becoming that kind of knowledge. Not because the people who lived through it are dying off, though they are. Because the experiences themselves are structurally incompatible with how life works now. You can't stream what it felt like to not know something and have no immediate way to find out.

Friction was the defining feature, though nobody called it that at the time. Information took effort. You went to a library, or you asked someone who might know, or you simply didn't find out. Communication had latency built in — you wrote a letter, waited days, received a reply that might or might not address what you'd asked. Choices were constrained by geography, opening hours, physical stock. None of this felt romantic while it was happening. It felt normal. The constraints were invisible in the way that water is invisible to fish.

What those constraints trained was a particular kind of cognitive stamina. When finding information costs time and effort, you develop a relationship with the information you do find. You hold it longer because acquiring it required investment. You synthesise rather than accumulate, because accumulation is expensive. You commit to decisions because reversing them means repeating the friction. Research published in Frontiers in Public Health found that intensive internet search behaviour measurably affects how we encode and retain information — when we know Google will remember for us, we let it, and something in the cognitive chain softens.

I'm not sure "softens" is the right word. It's more like a muscle that adapts to a lighter load. It doesn't atrophy overnight. It just gradually stops being asked to do what it once did.

The spatial memory research makes this uncomfortably concrete. A study in Scientific Reports found that people with greater lifetime GPS use showed worse spatial memory during self-guided navigation, and that the decline was progressive. The more you outsource, the less you retain. This isn't generalised anxiety about screens. It's measured cognitive change in a specific domain, and it maps onto the broader pattern: navigation, arithmetic, phone numbers, directions to a friend's house. Each delegation is individually trivial. Collectively, they represent a wholesale transfer of embodied competence to external systems.

The social texture was different too, in ways that are harder to quantify. Relationships were local, bounded, and — this is the uncomfortable part — more durable partly because leaving was harder. You couldn't algorithmically replace your social circle. You couldn't find a new community by searching for one. You were stuck with the people near you, which meant you developed tolerance, negotiation, and the particular skill of maintaining connections with people you didn't entirely like. I'm not romanticising this. Some of those constraints were suffocating. But they produced a form of social resilience that frictionless connection and equally frictionless disconnection don't replicate. The exit costs created a kind of civic muscle memory. When you couldn't easily leave, you learned to stay — imperfectly, sometimes resentfully, but with a persistence that builds something. What it builds is hard to name. Continuity, maybe. The knowledge that not every disagreement requires a door.

There's something related happening with culture, and it troubles me more than the cognitive stuff. Before global networks, culture varied sharply by place. Music scenes were geographically specific. Fashion moved through cities at different speeds. Slang was regional. You could travel two hundred miles and encounter genuinely different aesthetic assumptions. The internet collapsed that distance, and what rushed in to fill the gap was algorithmic homogenisation — platforms optimising for universal palatability, training data drawn overwhelmingly from dominant cultural archives, trend cycles that now complete in weeks rather than years. The result isn't the death of diversity exactly. It's the flattening of it. Regional difference still exists, but it's increasingly performed rather than lived.

I've written before about objects that outlive their context — the particular unease of encountering something from the pre-internet era that still functions perfectly but belongs nowhere. A compact disc, a theatre programme, a paper map. These objects assumed finitude. They were made for a world where things ended, where events didn't persist in feeds, where places could close and stay closed. When I handle them now, the dissonance isn't aesthetic. It's temporal. They're evidence of a different relationship with time itself.

That temporal difference matters more than people tend to acknowledge. Digital platforms compress time into an endless present. Feeds refresh. History scrolls away. Nothing quite arrives and nothing quite leaves. The pre-internet experience of time was linear in a way that sounds banal until you try to describe what replaced it. Waiting was an experience, not a failure of the system. Seasons structured cultural consumption because distribution channels were physical. Anticipation — real anticipation, the kind that builds over weeks — required scarcity. When everything is available immediately, anticipation doesn't intensify. It evaporates. There was a rhythm to cultural life that physical distribution imposed whether you wanted it or not. Albums had release dates that meant something because you couldn't hear them early. Television programmes aired once, and if you missed them, you missed them. Books arrived in bookshops and either found you or didn't. This sounds like deprivation described from the outside, but from the inside it felt like structure. Things had their time, and that time was finite.

Nicholas Carr has been making versions of this argument since The Shallows in 2010, and his more recent Superbloom extends it into the social fabric. The concern isn't that the internet is making us stupider in some crude measurable way. The concern is that it's restructuring cognition and social behaviour so thoroughly that we're losing the capacity to notice what's changed. When the baseline disappears, critique becomes harder. You need a reference point to identify a shift, and if nobody remembers the reference point, the shift becomes invisible.

This is the part that feels urgent to me. Not the nostalgia — nostalgia is cheap and usually dishonest. The urgent part is the comparative function. Remembering what pre-internet life actually felt like — not a golden-age fantasy of it but the real experience, including the boredom and the limitations — provides a structural check on the present. It lets you distinguish between convenience and cognitive cost. It lets you ask whether frictionless access to everything has made us richer or just busier. It lets you notice that memory itself has changed shape, not through damage but through delegation.

Without that baseline, you get what I'd call passive inevitability. The assumption that the present is the only way things could possibly work. That algorithmic curation is simply how culture operates now. That constant connectivity is a law of physics rather than a design choice made by specific companies for specific commercial reasons. Every system benefits from the erasure of its alternatives, and the pre-internet world is the most comprehensive alternative to the digital present that most living people can personally recall.

None of this is about wanting the old world back. Plenty of it was terrible. Information gatekeeping was often unjust. Communication barriers isolated people who needed connection. Cultural insularity bred ignorance as often as it bred character. The point isn't that friction was good. The point is that friction was informative. It taught things — patience, commitment, tolerance of uncertainty, the ability to sit with not-knowing — that the frictionless environment doesn't teach and may be actively unlearning.

I keep coming back to something that probably doesn't belong in this argument. When I was young, you could be unreachable. Not dramatically, not by fleeing to a cabin — just by leaving the house. No one could contact you. No one expected to. The hours between leaving and returning were genuinely yours in a way that requires explanation now, which is itself the point. That availability wasn't a moral obligation. That silence between people wasn't a crisis. That the default state of a human being was not "online."

Sources:

Lighter, Faster, Meaner

jQuery was 87KB. Lightbox2 was another 10KB, plus a CSS file and four UI images the library needed for its close buttons and navigation arrows. All of that is gone now — replaced by a single vanilla JS file under 7KB that does everything the old stack did. Gallery navigation with wrap-around, keyboard support, scroll locking, caption display, adjacent image preloading. Same IIFE pattern as the video lightbox I built last week.

The video player got its own round of trimming. I'd been eagerly loading hls.js and the video lightbox script on every page that contained video links — 149KB of transfer whether anyone clicked play or not. Now both scripts lazy-load on the first actual click. The hls.js library itself moved from a CDN with a seven-day cache to self-hosted with a one-year immutable header. And the encryption got a quiet upgrade: random IVs instead of deterministic MD5-based ones.

None of these changes alter how anything looks. Every byte saved is invisible.

The Sixteen-Byte Key That Broke Everything

MP4 files are embarrassingly easy to steal. Right-click, save as, done. For a blog that occasionally embeds short AI-generated video clips, this wasn't a theoretical concern — it was a guarantee. Anyone with a browser's developer tools could grab the file URL and download it in seconds. So I decided to replace the direct MP4 links with HLS adaptive bitrate streaming, complete with AES-128 encryption and burned-in watermarks. The kind of setup you'd expect from a proper video platform. On a static site hosted on S3.

That last sentence should have been the warning.

HLS — HTTP Live Streaming — works by chopping video into small transport stream segments, each a few seconds long, and serving them via playlists that tell the player what to fetch and in what order. Apple invented it for iOS back in 2009. The protocol is elegant: just files on a web server, no special streaming infrastructure required. A master playlist points to variant playlists at different quality levels, and the client picks the appropriate one based on available bandwidth. For a fifteen-second clip on a blog, adaptive bitrate is arguably overkill. I built it anyway, because the encryption layer depends on the HLS segment structure, and because I wanted four quality tiers from 480p to source resolution. The transcoding pipeline uses FFmpeg to produce each tier with its own playlist and .ts segments, then wraps them in a master .m3u8 that lists all four variants with their bandwidth and resolution metadata.

FFmpeg's HLS muxer is powerful and poorly documented in roughly equal measure. The flags for segment naming, playlist type, and encryption keyinfo files all interact in ways that the man page describes with the enthusiasm of someone filling out tax forms. Getting the basic transcoding working — four tiers, VOD playlist type, sensible segment durations — took maybe an afternoon. Getting the encryption right took three days.

The AES-128 encryption in HLS works like this: you generate a sixteen-byte random key, write it to a file, and tell FFmpeg where to find it via a keyinfo file. The keyinfo file has three lines — the URI where the player will fetch the key at runtime, the local path FFmpeg should read during encoding, and an initialisation vector. The player downloads the key, decrypts each segment on the fly, and plays the video. Simple in theory. The problem is that the key URI in the keyinfo file is relative to the playlist that references it, not relative to the keyinfo file itself, and not relative to the master playlist. Each variant playlist lives in its own subdirectory — 480p/stream.m3u8, 720p/stream.m3u8, and so on — while the encryption key sits one level up. So the URI needs to be ../enc.key. Get this wrong and the player fetches a 404 instead of a decryption key, and the error message from hls.js is spectacularly unhelpful. "FragParsingError" tells you nothing about why the fragment couldn't be parsed. I spent a full evening staring at network waterfall charts in Chrome DevTools before realising the key path was resolving to the wrong directory.

The watermark was its own category of frustration. I wanted the site domain burned into every frame — subtle, low opacity, bottom right corner. FFmpeg's drawtext filter handles this, and it's flexible enough to scale the text relative to the video height so it stays proportional across all four quality tiers. The filter string looks like someone encrypted it themselves: drawtext=text='plutonicrainbows.com':fontsize=h*0.025:fontcolor=white@0.30:shadowcolor=black@0.15:shadowx=1:shadowy=1:x=(w-text_w-20):y=(h-text_h-20). It works, but when you're chaining it with the scale filter for resolution targeting — scale=-2:720,drawtext=... — the order matters and the comma-separated syntax doesn't forgive stray whitespace. I had a version that worked perfectly at 1080p and produced garbled output at 480p because the scale filter was receiving the wrong input dimensions. The fix was reordering the filter chain. The debugging was two hours of staring at pixel soup.

Then came the client-side player. Safari supports HLS natively through the video element — you just point the src at the .m3u8 file and it plays. Every other browser needs hls.js, a JavaScript library that implements HLS via Media Source Extensions. The dual-path architecture isn't complicated in principle. Check if hls.js is available and MSE is supported, use it. Otherwise, check if the browser can play application/vnd.apple.mpegurl natively, and use that. The complication is that these two paths behave differently in ways that matter. With hls.js, you get fine-grained control — you can lock the quality tier, set bandwidth estimation defaults, handle specific error events. The native Safari path gives you a video element and a prayer. You can't force max quality on native HLS. You can't get meaningful error information. And iOS Safari doesn't support MSE at all, which means hls.js won't load, which means you're stuck with whatever quality Safari decides is appropriate based on its own internal bandwidth estimation.

For fifteen-second clips, this mismatch was particularly annoying. The whole point of locking to the highest quality tier is that short videos don't benefit from ABR ramp-up — by the time the adaptive algorithm has measured bandwidth and stepped up to a higher tier, the clip is nearly finished. I set abrEwmaDefaultEstimate to 50 Mbps in hls.js to force it straight to the top tier on page load. Safari users get whatever Safari gives them.

The lightbox player itself needed to handle a surprising number of edge cases. Autoplay policies mean the video has to start muted. The overlay should fade in immediately but the video element should stay hidden until the first frame is actually decoded — otherwise you get a flash of black rectangle before content appears. I used the playing event to reveal the video, with a four-second fallback timeout in case the event never fires. The progress bar is manually updated via setInterval because the native progress events fire too infrequently for a smooth visual. Right-click is disabled on the video element. The controlsList attribute strips the download button from native controls. None of this is real DRM — anyone sufficiently determined can still capture the stream. But it raises the effort from "right-click, save" to "actually write code," which is enough for a personal blog.

Deployment surfaced the final batch of surprises. The .m3u8 playlist files need to be gzipped and served with the right content type. The .ts segments need appropriate cache headers. And the encryption key files — those sixteen-byte .key files — need Cache-Control: no-store so that if I ever re-transcode a video, browsers don't serve a stale key that can't decrypt the new segments. I'd already been through the CloudFront HTTP/2 configuration saga, so I knew the CDN layer could hold surprises. The .key file caching caught me out anyway. Stale encryption keys produce the same unhelpful "FragParsingError" as a missing key, which meant another round of DevTools archaeology.

The whole system works through graceful degradation. No FFmpeg on the build machine? Video processing is skipped entirely and the links fall back to pointing at the source MP4 files. No video_processor.py module? Caught by an ImportError, build continues. No videos directory? No-op. I learned from the forty-five bugs audit that a static site generator needs to handle missing dependencies without falling over, and the video pipeline follows that pattern.

The opaque URL scheme was a late addition that I'm glad I thought of. Instead of exposing file paths in the HTML — which would let someone construct the master playlist URL and bypass the lightbox entirely — the build script generates a six-character content hash for each video and rewrites the anchor tags to use #video-{hash} with a data-video-id attribute. The JavaScript player reads the data attribute and constructs the HLS URL internally. The actual file structure is never visible in the page source. Again, not real security. But another layer of friction.

Was it worth it? For a personal blog with maybe a few hundred readers, building a four-tier HLS pipeline with per-video AES-128 encryption is — and I'm being generous to myself here — completely disproportionate. An <video> tag pointing at an MP4 would have been fine. But the MP4 approach bothered me, and sometimes that's reason enough. The fifteen-second clips play smoothly across every browser I've tested, the watermark is visible without being obnoxious, and the encryption keys rotate per video. The whole thing adds about forty seconds to the build for each new video, which is nothing given that the image pipeline already takes longer than that.

The drawtext filter string still looks like someone sat on a keyboard. Some things can't be made elegant. They can only be made to work.

Sources: