Changelog · a working build log

How this site actually got built

A living record of what I've changed on lilharper.dev, roughly in order, newest first. It's part scrapbook and part accountability, a way to watch the thing grow over time instead of pretending it arrived fully formed. Versions use the same date-stamped scheme the site's assets do (v=YYYYMMDD), so a version here maps to what was actually live rather than to some tidy release process I don't have.

v2026.08.22a latest

The VPS got smaller, and the homepage finally sounds like me

  • The front page stopped pretending to be a generic technical portfolio. It now says what this place actually is: a trans, autistic, ADHD little internet universe full of Hazel, Hilray TV, self-hosting, music, games, writing, Harley and whatever I overbuilt at 2am.
  • The current-project story matches the current server. Hazel, the site and Hilray TV are the things running now. Hytale, Enshrouded and Palworld live in one honest archive, with no join details, passwords or fake online indicators.
  • The infrastructure page was rebuilt around the smaller Debian VPS. It shows Caddy, the site APIs, Hazel's deploy path and the reverse SSH doorway to Jellyfin at home instead of a dead fleet of game servers.
  • The retired Minecraft files are no longer public. The deploy path now removes both old modpack downloads and the obsolete game-server JavaScript from the live server instead of leaving remote-only files behind forever.
  • The server got audited and then actually hardened. SSH is key-only with root login disabled; Hilray's key is tunnel-only; a persistent default-deny host firewall replaced the open input surface; LLMNR is gone; Debian and its kernel are current; the retired game routes and Docker socket are gone; Caddy and the API run read-only with dropped capabilities; secrets and visitor salts were rotated; logs are capped; spoofed Cloudflare identity headers are overwritten; and direct access to the main origin is refused.
v2026.08.04a

The site became a universe, and the universe got a signal tuner

  • The Harperverse is a real star chart built from the site's own page index. Personal writing, projects, mental-health resources, interests, writing and community pages form separate constellations. Search and constellation filters update both the chart and its keyboard-friendly destination list.
  • Visited public pages glow locally. The map reads the same browser-only journey data the site already keeps. Adult pages remain a collapsed, clearly marked region until the visitor has already passed the existing consent flow, and their individual page names are never written into persistent visit history.
  • “Throw me into the void” prefers somewhere unexplored. It uses the real index, respects the current search or constellation filter, excludes private and adult destinations, and falls back gracefully once every matching public star has been seen.
  • The global signal tuner reaches every page. Soft femme, dark masc, identity rift, feral web and quiet signal each alter the site's shared palette and atmosphere. The selection stays in this browser, composes with the manual hue control, and also reaches the standalone Myspace recreation.
  • Quiet signal is a state, not a grey colour preset. It removes the starfield, static, rifts, decorative animation and smooth scrolling, stops the ambient lofi, and pauses the floating music player without deleting its queue.
v2026.07.30a

Myspace came back, Tom is still first, and the Projects page finally shows its receipts

  • A complete Myspace recreation lives under About now. It started as a faithful blue-and-white profile, then got the customization it was missing: neon tiled wallpaper, Comic Sans, clashing borders, scrolling status text, profile stickers, blurbs, fake comments and exactly eight important things. Tom has the first spot, because some rules survived the old internet for a reason. Open the profile.
  • It is linked everywhere it should be. The About dropdown has a Myspace Recreation entry, the About page has its own callout, site search knows the page exists, and the discovery files include it. Clicking the Myspace logo originally reloaded the recreation forever; it now returns to the real lilharper.dev homepage.
  • The profile song is “This Calling” by All That Remains. The first custom control layer looked right and did not reliably play anything, so it was replaced with the native embedded player. Autoplay is still requested, clicking Play always works when a browser blocks it, and the familiar volume and mute controls stay visible.
  • The player took three attempts to get honest. The first version was quiet and depended on a custom unmute button. The second added a volume slider and mute toggle but still depended on the same unreliable control layer. The final version uses the official player's own controls and includes a direct YouTube link when somebody blocks embeds entirely.
  • The stretched Top 8 photos were a real CSS bug. Their HTML dimensions were still deciding the height after the grid decided the width, turning every picture into a tall sliver. The tiles now force a square aspect ratio and crop inside it instead of distorting the image.
  • The profile got the customization the first draft was missing. The plain recreation became the kind of page a sixteen-year-old would have guarded like a bedroom wall: moving rainbow chrome, impossible wallpaper, stickers, blink text, mismatched borders, fake moods, an online badge, “rawr xD,” and several color choices that should not be allowed near each other.
  • Myspace is a project, not an About-page easter egg. It now has its own card, technical notes and a current screenshot on the Projects page. The About menu and About page both still link to it, and the Myspace logo returns to the full site instead of reloading the profile forever.
  • The Myspace project card has its own receipts now. Its screenshot is cropped around the recognizable header and profile instead of shrinking the whole tall page into a card. It also has its own source, Top 8 and link counts, plus copyable snippets for the tiled wallpaper and the square-image repair.
  • The Projects page shows real code with the result beside it. Two excerpts from Hazel are paired with the Discord output they produce. Each can be copied normally or copied with Discord's fenced-code formatting already wrapped around it.
  • Source statistics have a definition now. Hazel, Hazel Care and lilharper.dev show physical source-line and file counts, dated and scoped so the numbers say what was counted. Backups, dependencies, generated builds and retired files are excluded.
  • Five old publication failures were cleaned up along the way. Three redirect pages gained proper main landmarks, and two prose lines stopped violating the site's punctuation rule. The publication checks are clean again, including the new authorship-language guard.
  • The authorship rule is enforced instead of remembered. The publication script now scans for wording that attributes the apps or website to automated authorship and refuses to release if it finds any. The existing mentions of local-model infrastructure and a retired feature are separate factual topics; neither is presented as the author of this work.
  • Music videos can follow you around the entire site now, including Myspace. Starting any verified showcase video opens one floating mini-player outside the part of the page normal navigation replaces. The video you choose becomes the beginning of a continuous Top Picks queue that automatically works through every other verified favourite and loops around. Previous and next buttons let visitors move around the queue without returning to Music, and the title updates to match each new video. Their first version waited for an unreliable ready signal from the privacy-enhanced YouTube embed, leaving them disabled with a loading cursor; they now switch the queue directly and work as soon as the player opens. The queue survives Myspace's full old-school page load too, restoring the current video near the same playback position and temporarily holding the profile song so two tracks do not fight each other. The player can collapse into a little queue bar, restore, switch cleanly to another starting point or close and stop.
  • Every revision was released through the normal safety path. Each publication passed the link, asset, heading, landmark, house-style, social-card and live-status checks, backed up the previous live copy, uploaded only changed files, purged the edge cache and verified the homepage answered afterward.
The completed Myspace recreation with neon wallpaper, profile columns and an embedded profile song
Myspace, rebuilt with the restraint of a caffeinated sixteen-year-old.
v2026.07.28a

233 wrong genres, a chip that split itself in half, and a like button that never worked on five pages

  • Genre Spotlight was wrong in more places than I first noticed. Twenty One Pilots and The Weeknd were both filed as Pop (they're not), Poppy and PVRIS were filed as Alternative when they're rock, Lil Peep was filed as Alternative instead of rap, and The Midnight was sitting in R&B/Soul when they're a synthwave duo. All five, plus Mithril Oreder, are top-150 artists so the fix lives in music-graph.js, which is the one place that can't disagree with the galaxy.
  • Then I asked for the rest of "Other" to actually get looked up instead of guessed. 233 artists across the full 489-artist list got checked against Wikipedia, Genius or AllMusic and reclassified where a source actually backed it up. genreKnown went from 247/489 to 473/489. Sixteen stayed "Other" on purpose -- a couple of name collisions with no way to confirm which artist was meant, a game soundtrack, a philosopher's spoken-word recordings, and two jazz vocalists the site's genre buckets were never built to hold. Forcing a bucket on those would've been a worse answer than leaving them alone.
  • A tag could split itself into two chips. The "win me over" pill row on the Love page had no rule stopping a long tag's text from wrapping mid-pill, so "a song you thought I'd like" broke across a line and rendered as if "like" were its own stray chip. .tag now gets white-space: nowrap, so a whole pill moves to the next line as one unit instead of tearing in half.
  • The Fluent in Dog photo was cropping out everyone in it. The shared about-hero photo box is 260x420, tuned for portrait headshots; Harley2.jpg is a wide 1200x900 shot with Harley at the far left edge and me crouching at the far right, so a centered crop straight down the middle cut out both of us and kept the empty grass in between. That page now gets its own wider box that actually matches the photo's shape. Also corrected: I've never worked as a dog trainer, only a paid dog walker, and the whole behaviorist side of this is a hobby and a passion, not a credential.
  • Reactions have been silently broken on five pages since they launched. The server checks every page id against a fixed allow-list before it'll count a reaction, and dogbehaviorist, fhrt, receipts, servers and shitpost were never added to it. Their like/love/OMFG WHAT buttons have been pressing into a 404 this whole time with no visible error. Added to the list; needs a VPS deploy of reactions_api.py to actually take effect.
  • The genre galaxy's connection lines are fractures now, not wires. Straight <line> edges became jagged multi-segment paths, seeded off each edge's own artist names and index so the crack pattern is identical on every load rather than reshuffling -- same rule as the rest of the galaxy's deterministic layout. Every line still runs from the same star to the same star and is still clickable through to its source; only the shape of the trip between them changed.
  • Reddit stays, minus the parts that point back to my actual account. I went back and forth on this one and landed on: the information can stay public (the dated 2017 headphone post, the 2018 A Perfect Circle ranking, the subreddit list on About), but the two direct reddit.com permalinks in the music page's receipts no longer render as clickable links, since following either one shows the poster's account on the post itself. The words are still quoted in full, verbatim, just not one click away from the account they came from.
v2026.07.27d

I measured how I actually write, found the site was not doing it, and then broke it twice fixing that

  • Click a photo and it opens at full size. Every content image on the site is now expandable, with the real pixel dimensions printed under it, arrow keys to move through the photos on a page, Escape to close, and focus returned to exactly where it was. It skips images inside links, decorative ones, and anything under 200px, because offering to enlarge a 165px picture to 165px is a promise you cannot keep.
  • The rifts shoot lightning now. The corruption arcs that were designed days ago finally exist: bolts fire out of a random rift every few seconds and briefly tear up whatever they land on. The CSS had been sitting there doing nothing the whole time because the script that drives it was never written, which I had reported as finished. It was not. It is now.
  • I measured my own voice instead of guessing at it. I ran my Facebook posts, my Instagram captions and my approved blog drafts through the same script as the site's prose and compared them on the same metrics. The result was blunt: I use contractions at roughly twice the rate the site did, first person two to three times as often, and I have never once written the phrase "rather than" in 27,904 words of my own writing. The site had used it 569 times. That is not my register leaking, it is somebody else's.
  • So 29 pages and 30 gated fragments got rewritten. "Rather than" is now at zero everywhere except this changelog, which is a build log and gets to keep its own voice. Contractions went from 196 per ten thousand words to 314. Filler like "actually" and "genuinely" dropped by nearly half, to inside the range my own writing sits in. Nothing on any page changed what it claims; every number, date, name and link came out the far end untouched, which I checked by fingerprinting each file before and after and comparing the tag structure and every digit.
  • The gated pages hold more writing than the rest of the site combined. 79,799 words across 30 fragments, against 66,011 in all 70 ordinary pages. They had never been through any of it, which I only discovered because I went looking for something else.
  • Two bugs of mine, same bug, twice. The contraction pass shipped 26 broken sentences to the live site: Some of it's who I'm, whoever you're, you deserve, the oldest kind of self-hosting there's. A contraction of is/are/am cannot sit at the end of a clause, and a rule that is right in the middle of a sentence is wrong at the end of one. I fixed those, added a deploy check so it could not happen again, then ran the same pass over the gated fragments and did it 30 more times. The check had not been looking at that folder. It is now, and both halves come back clean.
  • The Twitch tab is a button and a menu. Clicking the label goes to the stream page, the little arrow beside it opens the dropdown. It had been broken since I made the label a link while it still carried the dropdown's click handler: the menu opened and the browser navigated away about forty milliseconds later, so what you saw was a dropdown that did nothing.
  • The comfort pages stopped being a thunderstorm. The caregiver, pet regression, ABDL, ageplay and Harley pages had a warm lamp-lit theme underneath six rift cracks, a magenta nebula and a pixel grid, and the paragraph explaining that Mama's real name is deliberately kept off the page was hard to read through all of it. Those pages now turn the noise down: no rifts, no static, starfield at a quarter, and a warm wash in its place. The stickers stay, because a sticker is cute and a rift is corruption, and only one of those belongs on a page about feeling safe.
  • The 24/7 page doubled, and the new half is about coercive control. A healthy dynamic and an abusive relationship use identical vocabulary, so the words cannot tell them apart. Six things can: where the authority came from, what leaving costs, whether it ever gets reviewed, who else is allowed to know, who benefits, and what happens when you say no. Plus how people actually start one, the unglamorous infrastructure that keeps it safe, how it ends, and how it interacts with being neurodivergent.
  • The ranked artist list went from 250 to 489. That covers 4,632 songs, 88% of the library instead of two thirds. It costs something and the page says so: I have a genre on file for 247 of the 489, so about half the new arrivals sit in "Other" until I classify them. The counts are all real. The colour is the part that is not finished.
  • The shitpost wall is gone. Its 50 images were hotlinked to Facebook's CDN, which signs every link with an expiry roughly four days out, so the whole page was on course to become fifty broken boxes on 31 July with nothing in any log to say why. Instead of re-hosting them I removed the section. The changelog entries about building it stay, because this file is a record of what happened and editing that would break the only rule this site really has.
  • 29 images were still carrying camera metadata, including timestamps and a phone model, on a site that is otherwise pseudonymous. Stripped losslessly, pixels untouched, verified by re-reading every JPEG afterwards.
  • Also: the deploy script was refusing to run at all, reporting 26 em-dashes that did not exist, because PowerShell was reading UTF-8 files as Windows-1252 and the third byte of a trash-can emoji happens to be an em-dash in that encoding. The flame effect got harsher, with roughly double the throw and a chromatic tear that now shifts hue between frames. The blueprint installer accepts a 405 as proof a POST-only route loaded. Pipes between the loud nav buttons. A glow on Twitch that breathes faster when I am actually live, and one on Help in red, orange and white.
v2026.07.27c

Hazel plays my music into Discord now, and it took eleven bugs to get there

  • Hazel can play my music in a voice channel. Not by downloading anything: she relays what's already coming out of my speakers. My desktop audio goes into VoiceMeeter, which splits it to my headphones and to a virtual recording device, FFmpeg opens that device like a microphone, and the live audio goes straight into her voice connection. Nothing is fetched or stored, which is the entire reason this approach doesn't run into the problem that killed Rythm and Groovy. The full explanation is on the Hazel project page.
  • A now-playing panel with working transport controls. The YouTube Music desktop app exposes a small local server, so the Command Centre pairs with it once and can then show the current track and drive play, pause and skip. It's a remote rather than a second player, so pressing skip in the app, on my keyboard, or in Hazel all do the same thing to the same player.
  • Eleven distinct bugs, and I want them written down. A constant that got deleted by an edit landing on top of it, so the launcher died on startup. A console window flashing every five seconds because a background call spawned a process without hiding it. An FFmpeg output parser written for a format that changed five years ago, which reported zero audio devices on a machine with plenty. A version string with two numbers where the API demanded three. A URL with a prefix on it that the docs specifically say must not be there. A dialog nobody was told to go and look at. And a device name that differed only in one capital letter.
  • The one that's worth remembering. None of those eleven were syntax errors. Every single one parsed perfectly and failed only when it ran, which is exactly the class of thing that "the code is valid" can't catch. Somewhere in the middle of this I stopped trusting that and started scanning for names that get read but never defined, and that immediately caught the next one before it shipped.
  • Two things that sounded like my setup and were actually the code. Music through Discord sounded like a phone call, because voice channels default to a speech-optimised encoder and nobody had told it otherwise. And it stuttered because a live audio capture will silently throw audio away rather than wait when something downstream stalls. Both fixed; neither had anything to do with my microphone, my headphones, or my network.
v2026.07.27b

My own stream page, nine months of my own bios, and two bugs of mine that had been quietly lying to people

  • New page: my live stream. A page that becomes the stream when I'm live, player and chat together, and a real offline card when I'm not. It never says "offline" when what actually happened is that it couldn't reach the status API, because those are two different claims and only one of them would be true. There's a Live button in the top bar, and a red dot appears on it from anywhere on the site the moment I actually go live.
  • Nine months of rewriting my own bio. I exported my Instagram data out of curiosity and found it timestamps every profile edit, which means I had accidentally kept a diary. It's on the trans page now, in order, quoted exactly: "Gender under construction" in November, "☆ trans girl ♡" in March, and in May the name shortening to just Harper. That page used to say I'd never publish a timeline. I changed my mind halfway and rewrote the paragraph to say where the line now sits: no medical dates and no legal dates, ever. Bios I already posted in public are a different thing.
  • What the archive remembers. The about page now quotes my old Facebook bio back at me, including "I like talking, I like thinking, let me know if you want a conversation," which I apparently wrote as a mission statement for a Twitch channel about a decade early. Plus the thousand-word headphone review I posted in 2017, and playlists I've been quietly maintaining since then.
  • A real listening log, finally. The music page has always said it can't build a timeline because the library export has no dates in it. Spotify has since handed over a small streaming history that does. It's three weeks long and most entries are skips of a few seconds, so the page marks which is which per row rather than dressing seventeen plays up as "recently played."
  • Two of my own bugs, both of which were quietly lying. The floating reaction buttons were written to render the inline strip or the corner bubble, never both. That meant on the eleven pages carrying the inline strip (about, music, home, trans, harley and others) you scrolled past the top and had nothing left to press. Both render now, sharing one state, so pressing either updates both. Separately, the imgur file-extension check tried "gif" before "gifv", so every imgur video on the wall had been silently rendering as a frozen still.
  • The wall sorts by type. Images and Videos filters on the meme dump, driven by the actual file extension rather than a label stored when the link was first posted, which is exactly what broke my first attempt at it. Embeds wait for a click, the newest ten sit large at the top, and nothing autoplays.
  • The hit counter is padded, and says so. The guestbook counter now starts at 23,000, exactly like every counter on every homepage in 1998, because I asked for it to be. The digits still move when you reload, because the real per-browser count underneath is still real. The note under it explains all of that.
  • Also: 80 newly researched connections in the genre galaxy, every one sourced, taking it to 215 across 133 artists, with the seventeen artists I could find nothing verifiable for still named as such. Cleaning guides on the cannabis page. A nicotine harm-reduction section that attributes the "95% safer" figure rather than repeating it. Enshrouded on the infrastructure diagram and in the sidebar.
v2026.07.27

102 favourite games sorted into categories, a streamers page, and a near-miss with the deploy script worth writing down

  • My favourite games, properly sorted. 102 of them across eleven categories on the gaming page: MMOs, RPGs, Final Fantasy on its own because otherwise it buried everything, ARPGs, roguelikes, survival and building, shooters, strategy, co-op, the ones I own but need a group for, and what I'm actually hyped about.
  • Fourteen of those entries quote me from the time, pulled out of my own Facebook export rather than written for the page. The Guild Wars 2 one is twenty years of my life in a paragraph I don't remember writing. Only public posts and comments were searched, never messages, because my half of a DM is still half of somebody else's conversation.
  • New page: the streamers I actually watch. Seventeen Twitch channels with a player for each. The players only load when you press the button, because seventeen embeds firing at once would make the page miserable on a phone. It also doesn't claim to know who's live: checking that properly needs a Twitch API token a static site can't hold, so the page says so instead of faking a badge.
  • A near-miss worth recording. Fixing a missing comma in the deploy script's file list, I ran a find-and-replace across the whole file instead of just that array, and it inserted commas into the robocopy exclusion list, which is space-separated. That list is what keeps .env, .pem, .key and .bak files off the public site. It would have silently stopped excluding them. Caught it, restored the block, audited every other line in the file, and this entry exists so the shortcut looks as bad next time as it turned out to be.
v2026.07.26q

I counted every word I've written online since 2011, and the glow now travels around the button instead of spinning behind it

  • 813,025 words. I pulled my full Facebook and Instagram exports and counted everything I have personally typed on either platform since November 2011: posts, comments, captions, and messages I sent. That's about ten novels, or sixteen NaNoWriMos. The writing page has the breakdown.
  • My first count was wrong and it's worth saying why. I started from a derived spreadsheet and got 513,000, which felt low. It was: that dataset had deliberately stored messages as metadata only, so the single largest body of my writing wasn't in it, and the Instagram export is HTML with no JSON at all so none of it had been counted either. Going back to the raw exports found another 300,000 words. The lesson is that a tidy intermediate file isn't the same thing as the source.
  • What the numbers say about me, unflatteringly: my most-used content word is "people" (3,034 times), my median piece of writing is ten words long, my peak writing hour is 11pm, and I have typed "dog" 1,378 times against "love" 997 times. Harley wins by nearly four hundred.
  • Deliberately not counted, and not stored anywhere: who I was talking to, what anyone said back, and any per-person breakdown. Those conversations belong to the other people in them too, and none of them agreed to be a statistic on my website.
  • There's a photo of me and Wolfie in the little section now, framed like a polaroid taped to the page. It's displayed small on purpose: the file came through a chat upload that had already squashed it to 165 pixels wide, so blowing it up would just make it blurry. A snapshot is the right register for that section anyway.
  • The gaming page now has the actual arc, in phases: pure wonder as a kid, competitive shooters as a teenager, StarCraft and League as a young adult, and RPGs running underneath all of it the whole time. Plus what I'm obsessed with right now (Enshrouded, Tabletop Simulator for Gloomhaven and Magic, Palworld, Some of You May Die, Gamble With Your Friends) and the RPGs I keep going back to. The honest current filter is that I mostly play whatever lets me talk to people at the same time.
  • The glow on Blog, Shitposts and Harm Reduction travels around the border now rather than rotating behind the button. Getting there took two tries: rotating the element itself sweeps its own diagonal, and since these are wide pills that threw bright streaks above and below the nav bar. Animating the gradient's angle instead, and masking everything except the two-pixel ring, keeps the light on the border where it belongs.
v2026.07.26p

The nav got reorganised again, harm reduction became its own help button, and the blog can be pinned and replied to without clicking through

  • The glowing buttons were too much, so they're lighter now. Lavender instead of saturated purple/pink, the shine pooling under the button and around its border instead of blazing off it, and every animation slowed from about two seconds to four and a half. The floating search button got the same treatment for the same reason: it's on screen on every page, all the time, and it was pulsing like something was wrong.
  • Identity & Health and Neurodivergent both folded into About Me. The distinction that survives isn't topic, it's question: About Me answers "who is she" (trans, AuDHD, ADHD, dissociation, therapy, love, Harley, character sheet), Interests answers "what is she into."
  • Harm reduction is its own button now, in amber rather than lavender, because it isn't another page I'm proud of, it's the one someone might land on at 3am needing actual information. It should look like a first-aid sign, not a feature. There's exactly one button in that colour and there should stay exactly one.
  • The site changelog moved under Projects (reading it's closer to browsing what got built than to reading an essay), and Writing now holds essays plus the blog. Blog is deliberately the one duplicated entry in the nav: in the Writing menu because that's where you look for my writing, and still its own button because it's the page that changes most and burying it one hover deep is how a blog quietly goes dead.
  • The featured post on the blog is the pinned one now, if there's one, rather than always the newest. Pinning is how I say "start here," and before this it only reordered the list while the loudest card stayed whatever I wrote last.
  • Comments show on the featured post directly, so the post most people read is also the one they can reply to without a click-through. Other cards still link out; loading every thread on page load would be one network request per post.
  • I can pin and unpin from the blog page itself instead of opening the post in the editor. It flips one flag through a new endpoint rather than re-uploading the whole post, and pinning is exclusive, so pinning one thing unpins the last one in the same write.
  • There's a proper section about being little on the About page now, and it has its own soft pink styling: rounded, stars and hearts drifting along the top, a row of favourite things (Bluey and Muffin, Hilda, goldfish, Wolfie, soft clothes). Every other part of this site is CRT purple and sharp corners, which is the wrong frame for that paragraph. It reads as a different room in the same house. The decorative bits are all hidden from screen readers, so nothing there carries meaning the words don't already say.
v2026.07.26o

Blog and Shitposts are glowing buttons now, an ABDL page, and everything fades in as you scroll to it

  • Blog and Shitposts sit on their own at the end of the top bar, both wearing the site's glow instead of the old flat lime tint: the same purple/blue/pink recipe as the sidebar photo, shaped to a pill, with a blurred conic-gradient ring rotating behind each one. The bar used to allow exactly one loud item, on the theory that a second one cancels the first. That held while the highlight was a colour that fought the bar; two matched glowing buttons read as a pair instead. Two is still the ceiling.
  • The glow had to be tuned down from what looked best in isolation. The top nav is a horizontal scroll container, so anything drawn outside a link gets clipped flat at the bar's edge, and the sideways spill hazed over the "Connect" link next door. Checked in a real browser, tightened, and checked again.
  • New page: ABDL, in the Adult Dynamics menu behind the same age gate as the rest of that section. AB and DL are written as two separate things that happen to share a shelf rather than one blurry acronym, with a section on where the circles actually overlap: age regressors who wear specifically because of how well it triggers the headspace, people for whom there's no sexual element at all, people for whom it's purely a kink, and every mix in between.
  • Content fades in as you scroll to it now, everywhere on the site. Cards, callouts, section headings, and glossary rows arrive as you reach them instead of having already animated before you got there. Grid rows stagger against each other. If the script fails to load, or you have reduced motion turned on, nothing is hidden and nothing moves, because the hiding is applied by the script itself rather than sitting in the stylesheet waiting to be undone.
  • Cards lift slightly on hover now, the interactive ones only. Prose blocks deliberately don't move when the cursor crosses them.
v2026.07.26n

A shitpost feed for memes, the CG/L page got a couple more real details, and the sidebar photo/search button both got the glow treatment

  • New page: the shitpost feed. Paste a link (a YouTube video, a tweet, a TikTok, a Reddit post, an image, a clip, or basically anything else) and it embeds right there, pulled live from wherever it actually lives -- nothing gets uploaded or copied through this site, so the traffic and the credit both stay with the original host. It's saved in this browser only for now, which is an honest limitation rather than a hidden one: a different device starts with an empty feed until this gets a real shared backend later.
  • The Caregiver / Little page picked up two small, specific details instead of staying purely conceptual: what the actual comfort item is (a wolf plushie named Wolfie, promoted from stuffed animal to permanent coworker somewhere around year two) and a couple of the small, unglamorous rituals that turned out to matter more than the big ones.
  • The character sheet portrait and the floating search button now pulse with the same purple/cyan/green glow as the sidebar photo, instead of being the only two circular elements on the site that didn't.
  • The About Me dropdown was one long list of identity pages and hobbies mixed together; it's three now. About Me kept the core identity pages (about, character sheet, now, love, Harley). Food, Gaming, Music, Cannabis, and the new shitpost feed moved into a new Interests group. AuDHD and ADHD & Hazel Care moved out of Identity & Health into their own Neurodivergent group next to About Me, since browsing one of those pages usually means wanting the other, not the trans-health or harm-reduction pages they used to sit beside.
  • The Love & Attraction page names two more labels that were true the whole time but hadn't been spelled out: demisexual and sapiosexual, alongside the grey asexual label already there. In plain terms, a spark usually needs an actual bond to exist first and tends to get triggered by someone's intelligence and curiosity once it shows up, which is also why it's slow to arrive; the page now says outright that I deliberately keep sitting with someone even when nothing sparks right away, because for me that's often just what the process looks like before a connection has room to form.
v2026.07.26m

A searchable genre galaxy, post-video song suggestions, real infra nodes, a reworked Lights panel, and one lofi track instead of four

  • Google Home, the cameras, and the Govee lights are now real nodes on the infrastructure page's network diagram, not just prose further down the page. The camera node is labeled "Cameras," matching the existing writeup, since that section deliberately never names the brand.
  • Fixed a startup crash in the Hazel Command Centre ("bad screen distance") introduced while moving the Lights and Hazel AI panels back into the main window: a couple of Frame widgets were built with a two-part padding value that only Tkinter's pack/grid calls accept, not a widget's own constructor.
  • The Lights panel moved below Operations, full width, in a two-column grid of light cards instead of a narrow column squeezed beside it, and Operations' own DEPLOY / API SERVICES / SERVER / GAME SERVER & FLAVI groups can each collapse now, so the ones not in use can get out of the way.
  • Brightness is a small vertical slider now instead of a full-width horizontal one, it can target just the lights that are checked instead of always every light in the room, and its per-device Govee calls are staggered instead of firing all at once -- a likely fix for lights not responding to a group brightness change, based on the same kind of multi-light timing behaviour the Govee app itself has.
  • Renaming a light is just clicking its name now, no separate button, and lights can be reordered with per-card up/down arrows -- the order is saved and persists across restarts.
  • The ambient lofi player is one combined track now, not four switchable moods. Late night and Deep focus are stitched into a single longer piece; the mood picker is gone from the sidebar, leaving just play/pause and the volume slider.
  • A second research pass on the music page's genre galaxy added 15 more sourced connections (117 total now, up from 102, between 65 artists), each with a concrete basis and a source link like the rest of the dataset. The whole layout was re-simulated afterward so the newly-connected artists actually landed near each other instead of the map only being accurate for the older edges.
  • The genre galaxy is searchable now. Typing an artist's name shows a dropdown of matches; picking one highlights their star with the same pulsing purple/cyan/green glow as the sidebar photo (adapted for the galaxy's SVG stars) and opens their connections panel. Artists with no documented connection now get an honest fallback list of other artists in the same genre instead of a dead end, and every artist's panel now includes a few of their real top songs from the library export. Both areas link to each other, so browsing from one similar artist to the next doesn't require closing the panel and re-searching.
  • Pressing play on a showcase video now suggests what to watch next, right below the card, in the same video-plus-lyrics format as the rest of the showcase. Suggestions prefer a real, sourced connection from the galaxy's research (labeled with what the connection actually is: toured together, collaborated, same label) before falling back to same-genre picks, and each suggestion can be played too, cascading into more without ever repeating an artist already on screen.
  • A third research pass added 18 more sourced connections (135 total now, up from 117, between 87 artists) -- Kanye West, Kid Cudi, Common, Nas, Eminem, Wale, and a handful of others who previously had no documented connection now do. Only the newly-connected artists were re-simulated this time (not the whole map), so everyone else's star sits exactly where it did before.
Older build notes
v2026.07.26l

Blog comments, a suggestion box, and the front door actually leads with About Me now

  • Blog posts can be commented on. Every published post now has a comment thread underneath it: name required (unlike the guestbook's optional name), message required, honeypot-protected and rate-limited server-side, moderated the same way the guestbook already is. Reuses the existing boxed-form styling from the Work With Me page rather than inventing new CSS for it.
  • A suggestion box now lives on the Contact page, embedded right in the page instead of only being reachable through the floating "Ask me anything" bubble. It's the same inbox underneath, no new backend and no second moderation queue, just a second, more discoverable way in.
  • Fixed a real bug in the "latest post" card on the blog: it was picking whichever post happened to load last from the API and assuming that meant newest, which quietly broke the moment that assumption stopped holding, and the enlarged card could end up being an older post while a newer one sat in the small-card list beneath it. It now finds the actual newest post by comparing dates directly.
  • That featured post now shows in full on the page, body text and all, instead of a short excerpt behind a "Read the rest" link, so the latest post can be read start to finish without a click-through.
  • About Me is the actual front door again. A first-time visit still gets the entrance splash, but the "Enter the site" button had drifted to pointing at the tech dashboard instead of About Me, so every new visitor was landing on the dashboard regardless. Fixed to match how it was meant to work: first impression is About Me, "Home" in the nav still means the dashboard for everyone after that.
v2026.07.26k

Walked back some of the fire theme: the sidebar's calmer, the glow's not on fire anymore

  • The sidebar's green paint-splatter and the "RAWR XD" graffiti tag are gone, along with the small character-stat card (level/alignment, INT/CHA/DEX boxes, mood meters) that sat below the avatar. The separate "Your journey" progress widget stays untouched; it's a different piece.
  • The avatar's glowing ring is purple/cyan/green now instead of fire-toned (it was yellow/orange/red before). Same spinning halo, same animation, just recolored to match the rest of the site's palette instead of standing out as the one warm-toned thing on it.
  • That same glow now shows up in two more places, more subtly: a quick purple/cyan/green pulse under a button the moment you click it, and a faint ambient glow drifting behind the heading on every gated content page. Both respect reduced-motion the same way everything else on the site does.
v2026.07.26j

The background isn't empty anymore, there's static living behind everything

  • A faint, always-on noise/static layer now sits behind every page, a soft scanline-and-grain texture that slowly drifts through hue and breathes in opacity. This is separate from glitch.js's scroll/idle bursts, which are still occasional and loud; this new layer is the quiet, persistent version, closer to the "always a little noisy back there" look in the reference Steam-profile mockup.
  • Built additive, not a replacement. It sits directly on top of the existing starfield in the stacking order, both stay fixed behind every panel, and panels are already 75% opaque, so nothing about readability changes.
  • Reduced motion keeps a faint static texture but freezes the hue drift and breathing, same "freeze in place, don't fully vanish" treatment as the avatar ring's flame flicker.
v2026.07.26i

The glitch effect covers the whole site now, and Ask Me Anything got wired into the launcher

  • Signal corruption (glitch.js) got turned up instead of duplicated, same principle as always: it already touches every page equally (it hue-shifts whatever's underneath, text or image or canvas, with no idea what it's corrupting), so "apply the reference glitch-art's aesthetic to the whole website" meant amplifying this one mechanism, not building a second one. Fires roughly 2.5x more often, bands are bigger, and the colour range widened from a green/cyan-only rotation to also roll through magenta and amber, closer to the purple / green / fire variants in the reference images. Pages that see little or no scrolling now also get an occasional idle-triggered burst, so corruption isn't purely scroll-gated anymore.
  • "Ask me anything" is now one of the launcher's API installers, alongside Blog, Reactions and Guestbook. It quietly wasn't on that list before, which meant updating it needed doing by hand over SSH even though the button for exactly this already existed.
  • Reduced motion, the off switch, and the existing cooldown all still apply to the amplified glitch effect and its new idle trigger, unchanged.
v2026.07.26h

Pulled real reference in: the avatar has a flame border now, and the sidebar got a little terminal

  • The sidebar avatar ring now has a flickering flame-colored halo layered around the existing spinning purple/amber ring, echoing the actual fire border on Harper's own Discord avatar decoration rather than a generic glow.
  • "RAWR XD" got a real graffiti treatment. The sidebar's fixed tag (not the random rotating page-head ones) is now bold outlined spray-paint-style text instead of a filled pill, closer to the actual "RAWR!" profile effect it's referencing.
  • A small fake terminal now lives in the sidebar, a couple of "root@lilharper:~#" lines with a blinking cursor, purely decorative flavor text (aria-hidden), pulling in the terminal/CRT feel from the reference mockups without touching the sidebar's actual structure.
  • Also added a "Discord username or email" field to the "Ask me anything" widget, optional, so a message in Harper's DMs actually comes with a way to reply. Mirrors the same "Contact" field pattern the Work/Collaborate form already uses.
  • Reduced motion turns the new flame flicker and terminal cursor off cleanly, same guarantee as everything else.
v2026.07.26g

Turned it up again: the bratty pass wasn't actually loud yet

  • The last entry undersold itself. Everything it described was real and live, but sized and placed subtly enough that it read as barely-there instead of "RAWR XD." Same mechanisms, no new ones, just every dial pushed up: the two corner rifts nearly doubled in size and brightness and a third one joined them (top-right, bottom-left, and now a third along the left edge), the sidebar splat grew from a small blob into something that actually reads as a paint hit, and every sticker (sidebar and page-head) got noticeably bigger with a stronger glow.
  • Page-heads now guarantee a fire sticker alongside the random emoji + tag pair, so no page can luck into a quiet combination anymore. Both pools also grew: three more emoji (rage mark, rainbow, little devil) and four more chaotic tag lines.
  • Nav ticks pulse on their own now, not just on hover, and got a third bigger.
  • Mobile keeps the rifts instead of hiding them, just shrunk and repositioned so nothing overlaps content; the extra sidebar sparkle and third page-head sticker stay desktop-only so a small screen doesn't get crowded.
  • Reduced motion still turns all of it off cleanly, same guarantee as every pass before this one.
v2026.07.26f

The bratty pass, turned up: rifts, splatters, and stickers that actually shout

  • Round one of the bratty redesign read as tasteful, not bratty. This is the louder follow-up: two glowing "tear in reality" shapes now sit fixed in the corners of every page, hand-built jagged outlines filled with the site's own purple/pink/cyan gradient, slowly breathing brighter and dimmer rather than sitting static. They're decoration behind the content, not on top of it, so they never compete with anything you're trying to read.
  • A green paint splatter and a couple of jittering stickers now live around the sidebar photo ("RAWR XD" is currently living rent-free there), and every gated page's header now spawns a small rotating pair of stickers of its own on load, a random emoji and a random chaotic little tag, so it doesn't feel like the exact same two stickers on every single page.
  • I almost shipped a second "screen corruption" effect before catching that glitch.js already does that (the scroll-triggered signal tearing that's been live for a while). Didn't duplicate it, this pass leans on the one that already exists instead of building a competing version.
  • Everything here turns off under reduced motion, same as always: no rift breathing, no splatter pulse, no sticker jitter or flicker.
v2026.07.26e

The falling pixels got shapes, some of them are pride flags now, and buttons spark

  • The starfield used to be uniform little squares. Most still are, squares are staying the majority shape, but some now fall as circles, triangles or diamonds instead, tumbling slowly as they go, and sizes spread a lot wider: some noticeably tinier, some noticeably chunkier. Reads as varied confetti now instead of a grid of identical dots.
  • About one in eleven pixels is a tiny pride flag instead of a solid color: a small striped chip carrying the real trans flag colors or the five-stripe sunset lesbian flag colors, picked at random. They're sized a bit bigger than a plain pixel specifically so the stripes read clearly rather than blurring into one blob, and they keep their real colors even when the hue slider is spun, since the entire point of one is being recognizably that flag, not a mood-ring color.
  • Buttons spark now. Click anything with a .btn class and a small handful of colored sparks fly outward from wherever you clicked, in the site's own live palette. Built as a short-lived overlay above everything else on the page rather than drawn into the background canvas, so it's never accidentally hidden behind an opaque panel.
  • Reduced motion turns all of it off, same as everything else animated on this site: no sparks, and the starfield settles to one calm static frame instead of continuous fall.
v2026.07.26d

A bratty accent pass, and Muffin from Bluey makes a cameo

  • The site had the polish but not the personality, so this pass adds the personality back in. A small coloured "sticker tab" cycling pink, cyan and green now sits before every top-nav item (skipping the Blog pill on purpose, since lime is reserved to be the loudest thing on the page and a second colour riding along with it would undercut that). Every page's highlighted h1 phrase now gets a hand-drawn-style squiggle underline in the same three colours, drawn in with a marker-stroke reveal rather than fading in flatly. The logo gets a quick chromatic-aberration glitch flicker on hover, and every button on the site gets a small satisfying "squish" on press. A tiny sticker wobbles in the corner of the sidebar logo. Nothing here reproduces anyone's actual character art, it's all original shapes built to read as "Jinx/Smolder-coded" chaotic-cute energy rather than corporate.
  • Muffin, cousin of Bluey and objectively the most dramatic toddler in television, gets a cameo on the caregiver/little deep-dive page, in the section about what a little day actually looks like. It's a text reference and a fond joke, not an illustration; I'm not drawing Disney's character on my own site, and the callout box around it borrows the show's cream/sky-blue/dusty-orange palette rather than reskinning the whole page, since that page is the most vulnerable thing I've written here and didn't need a kids'-show costume over its entire length.
  • Everything respects prefers-reduced-motion, same as every other animated thing on this site: the squiggle just appears fully drawn, the sticker stops wobbling, the glitch and button squish both turn off.
v2026.07.26c

A floating "ask me anything" box that reaches my actual Discord DMs

  • There's a small chat bubble in the bottom corner of every page now (except the 18+ gated ones, where it would be the wrong tone). Click it, type something, and it goes straight to my Discord DMs, no account or reply thread needed. It's a one-way box, not a live chat: I read it and reach back out however I normally would.
  • The delivery goes through Hazel, not a channel webhook. The message posts to a public, rate-limited endpoint on the VPS (same trust level as the whitelist and work-request forms, since anything client-side can't hold a real secret), and Hazel polls that endpoint from my own PC and DMs me directly, the same pattern the work/collaborate form already uses. A hidden honeypot field catches most bots before Hazel ever sees them.
  • It parks bottom-right on desktop and swaps to bottom-left under 700px, on purpose, because that's exactly where Hazel's own avatar relocates to on narrow screens. Without the swap the two would sit on top of each other on a phone.
v2026.07.26b

Twenty new kink pages, and a nav menu that needed to learn how to scroll

  • Adult Dynamics went from nine entries to twenty-nine. Wax play, knife play, biting and marking, breath play, foot fetish, DD/lg and kink ageplay, ownership kink, objectification, roleplay, furry kink, praise kink, erotic humiliation, consensual non-consent, primal kink, sadism and masochism, exhibitionism, voyeurism, group play, auralism, and tentacle and monster kink each got their own page rather than a paragraph on an existing one, matching the pattern rope, impact, petplay and power exchange already used. BDSM in general, bondage, and 24/7 dynamics didn't get new pages, because those three are already covered properly on the existing pages and a fourth copy of the same ground would just be noise. Every one of the twenty is gated the same way the rest of this section is: a quick consent step, non-explicit writing, real safety information over technique, and a page that's a reference I keep for other people rather than a list of what I'm personally into.
  • A dropdown with twenty-nine items in it needed a ceiling it never had. The menu had no max-height and no scroll, which was fine at nine entries and would have been a flyout taller than some monitors at twenty-nine. It's capped at 70% of the viewport or 560 pixels, whichever is smaller, and scrolls internally past that.
  • The Adult Dynamics overview page grew four new sections to link all twenty pages in, sorted into sensation and body play, power and identity and roleplay, psychological and scene dynamics, and watching, sharing and fantasy, so the hub page reads as a map rather than a wall of links.
v2026.07.26a

A support button, a blog that's findable, and a quality pass over the whole thing

  • There's a Ko-fi button now, and it's the loudest thing on the site. "Buy me a Monster", lime green, sitting directly under the bird in the sidebar so it's on every page, plus a card on the contact page. It's a plain link rather than Ko-fi's own widget, which wanted to put two third-party scripts and a floating iframe on all thirty-eight pages, set a cookie nothing else here sets, and ignore both the hue slider and the light theme. A link opens the same page and loads nothing from ko-fi.com until somebody actually clicks it. Lime is used by exactly two things on this site and nothing else, on purpose: the moment a third thing shouts, none of them work.
  • The blog stopped being the middle item of a dropdown. It's its own entry at the far end of the top bar now, in the same lime as the Ko-fi button, because it's the only page here that changes and burying the only regularly-updated page one hover deep is how a blog goes quiet without anybody deciding to stop writing. Adding a ninth item cost about seventy pixels the bar didn't have, and since it scrolls with the scrollbar hidden, the overflow would have been silent and the thing pushed off the end would have been the item added specifically to be seen. The spacing tightens between 1024 and 1400 pixels to pay for it.
  • Nine finished essays are on the writing page, in their own words. Everything quoted on that page previously came from somewhere else on this site, which is a rigged sample: those are sentences that survived being turned into a website. These nine were written as essays. Each card carries the line I'd keep if I had to throw the rest of that piece away. They're heading to the blog rather than becoming pages, because a page is a thing I maintain and a post is a thing I wrote on a particular day and am allowed to have moved on from.
  • Six pages were carrying their own private copy of the stylesheet, and it had gone wrong. The age-gated pages each had an inline block redefining nine shared components, duplicated almost byte for byte across all six. It wasn't only redundant. The accessibility pass moved a lot of headings from h4 to h3 and h3 to h2 and widened the shared stylesheet to match, but the inline copies were never updated, so an h2 inside a boundary callout rendered completely unstyled on those pages while the identical markup looked correct on the safety page. All six copies are gone and the shared definitions do the work, which also means those pages now agree with the rest of the site on what a warning looks like instead of drawing it in a different colour.
  • Two classes called .status-grid were fighting, and the wrong one was winning. The Hytale server panel and the character sheet's status effects had both claimed the same bare class name. The character sheet's version sits later in the file, so it won everywhere, silently overriding Hytale's four-column layout and killing the responsive rule above it. Same specificity, no !important, nothing in the console: exactly the kind of collision that survives for months. It's scoped to the character page now.
  • Three page scripts were stacking event listeners on every visit. The router re-executes a page's scripts each time you navigate onto it, so reactions, the music charts and the games list were each adding another listener per visit, holding another closure, and by the fifth visit a single navigation was firing five identical requests. The guestbook panel had solved this properly months ago and the pattern was sitting right there. The galaxy had a related one: its Escape handler was an anonymous function, which meant the teardown couldn't remove it, so every trip through the music page left another handler on the document pointing at a panel that no longer existed.
  • The admin panels stopped downloading themselves to people who can't use them. Thirty-six kilobytes of control-panel code was loading for every visitor to the blog and the guestbook, whose only job was to check whether it should exist and correctly decide no. Same gate, evaluated before the request rather than after it.
  • Smaller things, in one breath. The hue slider and the search box both removed their focus outline without replacing it, and the search box is the one control the search dialog focuses automatically, so the only focused thing in that dialog was invisible to anyone navigating by keyboard. Icon buttons went from thirty-eight pixels to forty-four, along with the nav links, the reaction buttons and the guide's dismiss button, which was twenty pixels square and the only way to get her off a phone screen. The two photos that are the first thing on their page were marked lazy-loading, which is a straight loss on the image the browser should fetch first. Fonts now preconnect to the origin the actual font files come from rather than only to the one that serves the stylesheet. The galaxy's hover was doing two forced layouts and a walk over every star and link on every mousemove, and is now coalesced to one frame. Three hard-coded colours that the hue slider rotates everywhere else are variables again. There's a sitemap, which robots.txt had been promising for a while. The runbook and the vault are no longer indexable, the vault in particular being a secret page that was linked from every page on the site.
  • The diagnosis list on the AuDHD page is correct now. Two entries came off it because they aren't formal diagnoses and shouldn't have been written as though they were, and fibromyalgia went on, which is. Getting that list right matters more than most things on this site, because it's the page most likely to be read by somebody trying to work out whether the thing they recognise has a name.
  • Hazel is on Version 3, and the picture finally proves it. The Command Centre was rearranged around the thing I actually do most, which is watch output: four consoles in a two-by-two grid taking the whole right half, SSH and Docker taking typed commands directly, and the buttons that were competing with them shrunk and moved left. The command box works out for itself whether what you typed is for the Debian box, the Docker container, or PowerShell here. The screenshot on the lab page is now taken from the running window by a script rather than rebuilt from the layout code, which matters more than it sounds: a rebuild can quietly stop matching what the program does, and the last one had.
v2026.07.25c

Four new pages, a nav that fits on one line, and a security pass

  • The navigation was seventeen entries long. That's not a menu, it's a directory listing. It's eight now, grouped by what things actually are: About Me, Identity & Health, Adult Dynamics, Projects, Writing, Connect, plus Home and Infrastructure standing on their own. Nothing was removed and nothing got harder to reach; Music and Now moved under About Me, Dissociation joined the health pages instead of sitting under About, and Lab and Hytale went under Projects. Infrastructure deliberately stayed top-level rather than being filed under Projects, because burying it would quietly reverse a decision I made on purpose.
  • An accessibility pass, and the outline nobody could see. Every page now has a real <main> landmark and a skip link, because the sidebar and top bar together are about forty tab stops that a keyboard user was walking through before reaching a single word of content. Twenty-eight heading-level skips are gone: the card headings across the personal and reference pages jumped straight from h2 to h4, which reads to a screen reader as a section that's missing. Fixing that meant changing tags, so every affected CSS rule was widened to cover both, and I checked the computed sizes afterwards rather than assuming: 1.4rem is still 22.4px, 1.06rem is still 16.96px, nothing moved. Twenty-seven images gained real width and height attributes measured off the files themselves, so the pages stop reflowing as photos arrive. Contrast: eight patterns were failing AA, all near misses, including white text on the 18+ consent button at 3.76:1, which is the single worst place on the site to have it. All clear now. And the site's spelling picked a side, which it previously hadn't: seventy-odd words were British on pages that were otherwise American, sometimes in the same paragraph.
  • Love, like and share, on seven pages. Reactions on About, Music, Adult Dynamics, AuDHD, and the three new pages. Love and like count once per visitor and stay counted, share copies the link or opens the native share sheet, and the counts only appear once the server has actually answered, because a zero that corrects itself a second later reads as "nobody has ever reacted to this". The backend is the guestbook's shape for the third time now, which is the point: one pattern on that server rather than three. It stores no identifier at all, only a salted twelve-character digest per page so a repeat visitor can be recognized without their address ever touching the disk. There's no admin token on it, deliberately, because the only write is a counter that toggles and the worst case is an inflated number on a personal website. Hazel finally gets her "you can vote this one up, you know" line, which had been written and held back since before there was anything to vote on. She checks the buttons exist, aren't already yours, and aren't sitting disabled before she says it.
  • Hazel reminds you to look after yourself, and she leaves when you do. Water, stretch, food, medication, eyes, air. One line at a time, a long gap between them, never the same prompt twice in a row, and silent on the 18+ and dissociation pages where a chirpy interruption would land badly. Every nudge carries its own "not today" button that mutes the reminders until midnight without costing you her or the search behind her, and the timer lives in storage rather than in the page, so clicking a link doesn't restart the clock and turn a reminder into pestering. She doesn't ask you to confirm you did any of it, because a reminder you have to tick off is just a task, and the desktop app already exists for people who want that. Separately: click away to another window and she walks off the edge of this one, slowly enough to actually watch, and coming back brings her strolling in from the other side with something to say about how long you were gone. Flick away for four seconds and she is just back, no comment. That hangs off window blur rather than the tab-hidden event on purpose, because a hidden tab has stopped painting and an exit nobody can see isn't much of an exit. And where the browser supports it she will offer to leave properly: a small chromeless window that floats above everything else on the desktop, which she paces about in until you send her back. A page can't draw outside its own window, so that's genuinely the closest thing to letting her out there is.
  • The landing page discharges properly now. Electricity gathers along "Hey, I'm Harper.", tears outward from the words themselves, and the residue ricochets around the window taking a new color off every wall it hits. Two things were wrong before: the heading was still invisible when the bolts fired, so the sequence was about a piece of text that hadn't arrived yet, and the origin was a hardcoded fraction of the window height that sat ninety pixels below the heading on desktop and a hundred and eighty-five below it on a phone. It's measured off the real heading now, and re-measured when the window resizes. The bounce phase also went from four single-segment sparks to twelve with trailing bolts, because the old version spent two of its three seconds looking like dust.
  • Love & attraction. A new page about how I actually work: my love languages, what I'm drawn to in other people, two things about my identity that weren't written down anywhere else on this site, and a genuinely unreasonable amount of detail about food. The hotdog section is load-bearing.
  • Safety, consent and meeting standards. The longest page on the site, and the one I most wanted to exist. Consent done properly rather than gestured at, negotiation, safewords and the traffic-light system, how to vet someone, how to meet a stranger from the internet without it going wrong, what the different dynamics actually involve, aftercare and drop, and a concrete list of coercion patterns. It sits outside the age gate on purpose, for the same reason the harm reduction page does: a click-through costs the most for the person who needs the information most.
  • Therapy and mental health. What a psychiatrist actually does versus a psychologist versus a registered psychotherapist versus a coach, how to check somebody is really registered and which register to check, what the common modalities are without the hype, and the part I care about most: you're the expert on your own experience, fit matters more than credentials alone, and changing therapist isn't failure.
  • A blog, with a control panel behind it. Posts and news items, written from a panel that only appears if you know to ask for it, gated on the same admin token as the guestbook. Drafts stay invisible to everyone but me, edits keep their original publish date, and deleting anything writes a backup first. Same architecture as the guestbook backend, because it worked.
  • The site has screenshots of itself now. The "site over time" section had been an empty placeholder with a note-to-self in it explaining how to add one. There are eight captures in there instead, plus four on the projects page, all taken at 1440px with the guide and the intro sequence suppressed so they show the pages rather than a character standing in front of them. They're all from today, so this is the starting point rather than a history; the comparisons get interesting from here. Taking them also turned up something the text sweep couldn't see: seven em-dashes living inside JavaScript strings rather than in the HTML, including one in the galaxy legend that had been on screen the whole time. Those are gone too.
  • Cannabis has its own page again, and the work form asks properly. The strain reviews, the product photography and the trichome shots were living at the bottom of the harm reduction page, which never fitted: everything above them is sourced from DanceSafe and TripSit and written for somebody else's safety, and that's a personal log of something I'm actually prescribed. Two different kinds of page wearing one title. Harm reduction also moved out of Adult Dynamics and in with the health pages, because someone who lands on it's looking for safety information rather than for kink, and it was sitting between rope and power exchange. On the work page, the segmented toggle became two cards: as a toggle, "friendship application" looked like the off state of "work request", which undercut the exact point the page makes about it being a real option and not a joke button.
  • Series I have gone all the way into, corrected. S.T.A.L.K.E.R., Tropico and Warhammer are off the list. Owning six of something because it was in a bundle isn't the same as having gone all the way into it, which is what that section claims to be about. Blizzard is on it instead, counted by hand and flagged as such, because none of it ships on Steam and a franchise section built purely on my Steam library was quietly omitting my favourite publisher and the single largest block of hours on the page. Both lists live in the rendering code rather than in the generated data file, so the next library sync can't undo either of them.
  • The hours chart, rebuilt honestly. It listed four MMOs and called Diablo one of them, which it's not. It now runs to forty-eight entries covering Battle.net, Riot, browser games, consoles and launchers that predate Steam, and it separates what Steam measured from what I'm going from memory on, because those are two different standards of evidence and pretending otherwise would be the easy lie. The total went from twenty-four thousand hours to about fifty-two thousand, which averages to five hours a day across twenty-eight years. That average is doing a lot of hiding: the real shape is twelve and sixteen hour WoW days running for months at a time, next to years where it wasn't installed.
  • A security and privacy pass. Player and mod names coming off the game server were being written straight into the page as HTML, which meant anyone who could set a display name could have run script on this site and read my admin token. They're text nodes now, the way the guestbook always did it. Guestbook visitor addresses are hashed the moment they arrive rather than stored and then hidden. The adult pages no longer write their names into your browser's permanent storage, because a durable record of what someone read here's not mine to leave on their machine. YouTube embeds stopped leaking which page you were on. The repo finally has a .gitignore, and /content/ has a robots rule, which was the only part of the age gate doing real work.
  • And a long list of things that were quietly wrong. Pages describing an AI assistant I deleted months ago. The guestbook backend described as unbuilt on one page and live on four others. A page asserting a diagnosis the whole site is careful not to claim. Community guidelines that read like a terms of service on a page advertising that they don't. An Android stat that was off by a decade. None of it was visible unless you read two pages side by side, which is exactly why it survived this long.
v2026.07.25b

Nineteen years of Steam, and a moderation panel

  • The whole Steam library, searchable. 458 of the 520 games I own, in a scrollable list you can type into to check whether I have something. The gap is bundle filler and delisted things, and I'd rather say so than pad the list to make the numbers agree. It sorts A-Z or by hours once the sync script has run, and there's a genre breakdown and a franchise count underneath, both counted from the data rather than from memory.
  • The numbers, including the unflattering ones. 520 games, 9,014 hours, CDN$3,311 spent, thirty-six cents an hour. And 131 games I've never once opened, which is CDN$365 of good intentions. There's a chart of how the time is actually distributed, and the two longest bars are the two I'm least proud of. A library page that only shows the hours is telling you half the truth.
  • A sync script instead of a live fetch. tools/steam-sync.py pulls from the official Steam Web API on my machine and commits the result. The API key can't live in a static site's JavaScript, Steam doesn't allow browser requests anyway, and this way the page has no runtime dependency on Steam being up. Hand-assigned genres survive between runs. There's deliberately no "date added" anywhere, because no public Steam API exposes it and I'm not going to fake a timeline.
  • Guestbook moderation. A panel that only exists if you know to ask for it, gated on the same admin token the rest of the server uses. Hide something without deleting it, delete it permanently, or wipe the wall entirely. Deleting takes two clicks; wiping makes you type it out, and the server re-checks that rather than trusting the browser. It also saves a backup file before wiping. Repeat posters show up as a short code rather than an address, so moderation can tell "same person again" without anyone's IP ever being displayed.
  • A horizontal scrollbar on the music page, gone. The genre breakdown was a two-column grid with hard pixel floors adding up to more than a phone screen, so the whole page could be dragged sideways at 390px. It stacks now.
v2026.07.25

A front door, a search box, and a proper read-through

  • The site got a front door. A landing page that shows once per visit before the site proper, with its own smoke-and-flash intro, a starfield, and one button that says "Enter the site". It's remembered for the rest of your visit so clicking around never bounces you back out to it, and if your browser has storage blocked it quietly skips the splash instead of breaking the page.
  • Ambient lofi, generated live in your browser. There's no audio file and nothing streamed. It's a small Web Audio loop I synthesize on the fly: four chords on a cycle, oscillators drifting a few cents apart for a tape-warble feel, a filter breathing open and closed, and vinyl crackle built from generated noise. Four moods, a volume slider, and it remembers what you picked. I went this route because hosting an actual lofi recording on a public site is a real licensing question, and generating it means there's nothing to license and nothing extra to download.
  • Site search. Hit / or Ctrl+K anywhere and you get a search box over every page on the site, except the one that's supposed to stay hidden. Arrow keys to move, Enter to go, and an empty query just lists everything so it doubles as a map. 18+ pages are marked as such in the results.
  • Related-page cards. A "Keep exploring" block now sits above the footer on every page, picking a few rooms that connect to the one you're in, scored by shared tags. One firm rule in the scoring: a tech page never funnels anyone into an 18+ page.
  • Four more pages in the Dynamics section: rope bondage and safety, impact play, power exchange and D/s, and petplay. All education-first, all behind the same consent step, and all with their bodies kept out of the page source.
  • A full read-through. I went through every page on the site start to finish and fixed the typos, tense slips, stray punctuation, and sentences that only ever made sense inside my own head. Nothing to look at, badly needed.
v2026.07.24b

The guestbook goes shared, the map goes live

  • A real guestbook backend. A small Flask blueprint for the VPS that keeps entries in a single JSON file, written temp-file-and-rename so a crash mid-write can't shred it. Three notes per day counted per connection rather than per browser, a honeypot field that returns a cheerful success to bots so they can't tell they've been caught, control characters stripped, and a hide switch for anything abusive. Built and tested; the page already knows to fall back to browser-only storage when the server isn't answering, tell you so plainly, and post what you wrote the next time it is.
  • Retro guestbook front end. A hit counter that ticks like an LED odometer, a row of old-web badges, and a scrolling marquee, because the whole point of a guestbook is that it looks like 2003.
  • The infrastructure map became properly interactive. Hand-rolled SVG topology with no chart library, matching the site's zero-dependency habit. Hover or focus a node and its connections light up while everything else dims, little dots travel the traffic edges, and the VPS node carries a genuinely live status dot fed by the same status API the rest of the site already reads. Unreachable server gives you a neutral dot rather than an error.
  • ADHD and self-care merged into one page. They were two pages saying half a thing each. The app and the reason it exists read far better together, so the old URL now redirects instead of rotting.
  • Work / Collaborate page. Two tabs: a work request and a friendship application, the second of which is a real option and not a joke button. Submissions go out along two independent paths, so if one is unreachable the message still lands.
  • BDSM & kink page added behind the consent step, alongside the caregiver/little and polyamory pages.
v2026.07.24

Persistent music + the personal pages

  • Music that doesn't stop. Rebuilt page navigation as a client-side router so the sidebar Spotify player keeps playing when you move between pages, instead of restarting on every click. It was the single most annoying thing about the site, and it bothered me long before I sat down to fix it.
  • Personal Dynamics hub. A new public page pulling together the honest, human side of things: caregiver/little, comfort items, and a link through to the deeper pages.
  • Polyamory & relationship anarchy page. What the words actually mean, why non-hierarchical fits me, and real resources rather than vibes.
  • Sensitive-topic theming. Personal and adult pages now get an automatic red "read with care" treatment plus a consent step before you go in, so nobody lands somewhere heavier than they expected.
  • Guestbook spam limit (2 signs / 24h) and real infrastructure cost numbers on the infra page.
  • Medical cannabis note added to the drugs page, putting it on the record that I'm a prescribed patient.
v2026.07.23

Identity pages & the sidebar redesign

  • Trans, ADHD & Hazel, and self-care pages. The more personal, written parts of the site, and the ones that took the longest to decide how public to be about.
  • Sidebar redesign. Moved the hue picker and theme toggle into the sidebar, trimmed the top nav down to curated quick-links, and added the RPG stat panel.
  • Hidden achievements. A Konami code and a secret door, unlockable site-wide and tracked on the character sheet.
v2026.07

The interactive core

  • Infrastructure dashboard. A live-flavored homelab topology map with a terminal panel, and the piece the homepage now leads with.
  • Music page. Top songs, genre and decade breakdowns, and listening stats.
  • Drugs / harm-reduction page. Honest, judgment-free education, plus my own cannabis strain reviews and microscope shots.
  • Guestbook. Old-web energy, entirely on purpose.
  • Character sheet. The RPG framing for an about-me page, stats and all.
Foundations

The bones of the site

  • Shared shell. One sidebar and topbar built from a single nav list, so every page stays consistent and adding a page is a one-line change.
  • Live theming. A hue slider that re-tints the whole palette, plus a light/dark toggle, both remembered between visits.
  • Animated starfield background and the overall visual identity.
  • Self-hosted from the start, on my own VPS behind Cloudflare and Caddy, deployed with a script I run by hand.

📸 The site over time

Screenshots as the look evolves, which is the visual half of the record. Written entries are good at explaining why something changed and useless at showing how different the place actually looked. All of these are from 25 July 2026, so they're the starting point rather than a history; the comparisons get interesting from here.

Screenshot of lilharper.dev: the front door, with the live homelab topology sitting above featured projects.
The front door, with the live homelab topology sitting above featured projects.
Screenshot of lilharper.dev: infrastructure as a network map. The VPS node reads the real status API.
Infrastructure as a network map. The VPS node reads the real status API.
Screenshot of lilharper.dev: the hours chart. Steam-measured figures and my own estimates are kept visibly apart.
The hours chart. Steam-measured figures and my own estimates are kept visibly apart.
Screenshot of lilharper.dev: the genre galaxy. One star per artist, positioned by researched connections.
The genre galaxy. One star per artist, positioned by researched connections.
Screenshot of lilharper.dev: the favourites showcase. Every video checked against YouTube's oembed before it went in.
The favourites showcase. Every video checked against YouTube's oembed before it went in.
Screenshot of lilharper.dev: the character sheet, with achievements as a card grid.
The character sheet, with achievements as a card grid.
Screenshot of lilharper.dev: the safety, consent and meeting standards page.
The safety, consent and meeting standards page.
Screenshot of lilharper.dev: love languages, and a genuinely unreasonable amount of detail about food.
Love languages, and a genuinely unreasonable amount of detail about food.