<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="/css/simple-atom.xslt"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title>Death.au's Domain posts tagged HTML</title>
  <subtitle>Thoughts, stories and ideas.</subtitle>
  <link rel="self" href="https://death.id.au/tag/html/atom.xml" type="application/atom+xml" />
  <updated>2026-03-23T05:11:12Z</updated>
  <author>
    <name>Death.au</name>
  </author>
  
  <id>https://death.id.au/tag/html/</id>

  
  <entry>
    <title>Service Worker Side Template Rendering</title>
    <id>https://death.id.au/b4.0018/</id>
    <updated>2025-09-30T04:05:43Z</updated>
    <published>2025-09-30T04:05:43Z</published>
    <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><h1>Service Worker Side template rendering</h1>
<p>I've recently been inspired by <a href="https://ahastack.dev/">The AHA Stack</a> and wanted to look into building something with it. Astro in particular reminds me of building with 11ty, it's just not (necessarily) building a static site. But I also want to look into more offline-capable web development, which is pretty much impossible with server-side rendering like Astro. If only I could get Astro to run on the client side in a service worker...</p>
<p>I did look into that possibility and found <a href="https://dev.to/thepassle/service-worker-side-rendering-swsr-cb1">one attempt</a>, which, if I'm honest, went mostly over my head. My takeaway is that it wasn't yet feasible back in 2022 and while things may have changed now, no one else has pursued the idea. I don't think I have the expertise to pull something like that off, but I was inspired to look into perhaps doing something similar, but more simply.</p>
<p>So, I've taken a crack at something... I don't know how useful it is to everyone else, but I think it might have some legs. I've got a server set up with some <a href="https://mustache.github.io/">Mustache</a> templates, then I have a web page with <a href="https://htmx.org/">HTMX</a> set up to request those templates alongside data from a JSON endpoint. For this example I'm using the omg.lol API to get statuses (I'm toying with the idea of rebuilding neighbourhood.omg.lol with this stack, but that's another story). So on my HTMX-enabled HTML page, I have included this:</p>
<pre><code class="language-HTML" data-lang="HTML">&lt;section id=&quot;statuses&quot; hx-trigger=&quot;load&quot; hx-swap=&quot;innerHTML&quot; hx-get=&quot;/templates/statuses.mustache&quot; hx-vals='{&quot;@url&quot;:&quot;https://api.omg.lol/statuslog/latest&quot;}'&gt;&lt;/section&gt;
</code></pre>
<p>This triggers a request to <code>/templates/statuses.mustache</code>, with <code>@url=https://api.omg.lol/statuslog/latest</code> encoded into the query parameters (if I'd used <code>hx-post</code>, it would be passed through the body). This request will be picked up and intercepted by my service worker like this:</p>
<pre><code class="language-JavaScript" data-lang="JavaScript">self.addEventListener('fetch', async event =&gt; {
  if(new URL(event.request.url).pathname.startsWith('/templates')){
    event.respondWith(templates(event.request))
  }
  else {
    //TODO: serve other stuff from cache
    event.respondWith(fetch(event.request));
  }
});
</code></pre>
<p>(I could and perhaps <em>should</em> change this to check for a pathname ending in <code>.mustache</code> rather than starting with <code>/templates</code>, but I'm still just playing around here).<br />
The important thing here is obviously the call to <code>templates</code>, which I will break down into pieces. The first thing it does is ditch any parameters and get the template file itself:</p>
<pre><code class="language-JavaScript" data-lang="JavaScript">async function templates(request) {
  const url = new URL(request.url)
  const templateUrl = new URL(url.pathname, url.origin)
  
  // first try and get the template
  const templateres = await getTemplate(templateUrl) 
  if(!templateres.ok) return templateres // return the result on failure
  let template = await templateres.text()
  
  ...
</code></pre>
<p>The <code>getTemplate</code> function call there is just a basic &quot;check if it's in the cache, if not go fetch it&quot; kind of function:</p>
<pre><code class="language-JavaScript" data-lang="JavaScript">async function getTemplate(url) {
  // first check the cache
  const cache = await caches.open(TEMPLATE_CACHE)
  let response = await cache.match(url)
  // if not in cache, fetch it and put it in the cache
  if(!response){ 
    response = await fetch(url)
    if(response.ok) cache.put(url, response.clone())
  }

  return response
}
</code></pre>
<p>Once I have the mustache template in hand, I need the data to populate the template. I've built it so that you can either pass data through the request, or fetch the data from elsewhere using <code>@url</code> and other <code>@</code>-prefixed parameters to build up a request. At the moment I'm assuming such a request will return JSON, but this should probably be made more explicit in the future.</p>
<pre><code class="language-JavaScript" data-lang="JavaScript">  ...

  let data = {}
  let requrl = null
  let reqinit = {}
  let params = {}
  // check if anything was passed through search params
  if(url.searchParams.size &gt; 0) params = parseSearchParams(url.search)
  // check if anything was passed through the body
  const body = await request.text().trim()
  if(body) params = {...params, ...parseSearchParams(body)}
  
  // @url is a url to fetch data from
  // @&lt;other&gt; is a request init param
  // everything else is data for the template
  Object.entries(params).forEach(([key, value]) =&gt; {
    if(key == '@url') requrl = value
    else if(key.startsWith('@')) reqinit[key.slice(1)] = value
    else data[key] = value
  })

  // if we have a request url, go fetch data from the request
  // TODO: caching?
  if(requrl) {
    const responsedata = await fetch(requrl, reqinit).then(r =&gt; r.json())
    data = {...data, ...responsedata}
  }
  
  ...
</code></pre>
<p>finally, once we have the data (either from the request or passed through), we can use that to render the template and return the rendered HTML as a response for HTMX to swap in</p>
<pre><code class="language-JavaScript" data-lang="JavaScript">  ...
  // render the template with any data we have
  const output = Mustache.render(template, data)

  // finally, create and return the response
  return new Response(output, {
    status:200,
    headers:{ &quot;Content-Type&quot;: &quot;text/html&quot; }
  })
}
</code></pre>
<p><em>Et voilà</em>, service-worker-side rendering from templates stored on the server and cached locally. Obviously there's a lot of room for improvement here, especially to do with the caching strategies. But I can imagine building a PWA like this, with locally cached templates and the ability to fetch data from an external API.</p>
<p>I am very tempted to continue working on this, but I would also like to know how useful this is. Is this just something fun, or are there practical applications? Is there a future for service workers beyond caching strategies? Please, let me know. These are discussions I want to be having.</p>
</div></content>
    <link rel="alternate" href="https://death.id.au/b4.0018/" />
    <summary type="html">HTMX + Mustache with a service worker fetching external data and rendering HTML from a template. Fun? Practical? You decide!</summary>
  </entry>
  
  <entry>
    <title>My new website (again)  - 11ty+Friendica</title>
    <id>https://death.id.au/b4.0017/</id>
    <updated>2025-09-22T00:33:00Z</updated>
    <published>2025-09-22T00:33:00Z</published>
    <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><h1>My new website (again) - 11ty+Friendica</h1>
<p>So, I've re-jiggered my website <em>again</em>, and moved away from Ghost, back to 11ty. As I mused in a <a href="/B4.0016">previous blog post</a>, I wanted a simpler ActivityPub id, and more control over the handle (i.e. the ability to use <code>death.au</code> as the preferred username). I already had that in a self-hosted Friendica instance, but if I moved that to my main domain, that would prevent my website from showing up at the main domain. Well, with a little help from Cloudflare, I've managed to work around that.</p>
<p>I wanted to write about what I've set up here. I'll start with the basic part:</p>
<h2>Eleventy static website</h2>
<p>I've used 11ty in the past, and I really like the flexibility it gives me in the output. I can keep my pages and articles as simple markdown, or go for an all out html bonanza and 11ty will assemble the pieces and spit out html files (or other kinds of files) for me, so I end up with a collection of files that can just be served on the web in the most basic of manners.</p>
<p>Also, it allows me to set up page metadata for routing. I've got a URL scheme of my own I'm using, very loosely inspired by Johnny Decimal, whereby all my blog posts are at <code>/b4.xxxx</code> (where <code>xxxx</code> is just an incremental id). I've also got some small portfolio pages (that I still want to expand on) under <code>cv.yy</code> and short-form microblog-style posts under <code>/n.zzzzzzzz</code>(where <code>zzzzzzzz</code> is actually a base 36 encoded timestamp). Alongside some other things like a summary and featured images (if applicable) I have a lot of control, with very little markup.</p>
<p>Setting up hosting for the pages is simple on Cloudflare. I set up a pages project pointing at my 11ty repo, tell it what command to use to build and what folder the resulting files will be in to upload and now every time I commit and push, my website is automatically built and published. I'm already using Cloudflare's DNS, so it's trivial to point <a href="https://www.death.id.au">https://www.death.id.au</a> to this pages project and my website is good to go.</p>
<h2>Friendica for the Fediverse</h2>
<p>As stated above, I was already hosting a Friendica instance as my primary window into the Fediverse. I have it set up in a docker container (via docker compose) on a webserver I'm paying for, behind a reverse proxy. With that all set up it was surprisingly easy to add a route for the reverse proxy to serve up Friendica on <a href="https://death.id.au">https://death.id.au</a>. To reconfigure Friendica itself for the new domain, there's just a command that needs to be run in a console to update the database and send information out about the new domain. And because I'm still hosting it on the old domain as well, I shouldn't be losing anything coming in, even if my followers' details don't get updated right away.</p>
<p>Friendica is an amazing piece of software. Not only does it federate with other ActivityPub services like Mastodon, it also supports &quot;Groups&quot; to follow Lemmy boards, there's a plugin for BlueSky, it supports OStatus and diaspora (not that I follow anyone on those protocols at the moment), it even supports following basic RSS feeds; all in the same interface. On top of all that, it even has Mastodon-compatible API endpoints, meaning I can use most Mastodon apps/clients to interface with it, including my favourite app, Fedilab.</p>
<p>I have my instance set up as a single-user instance, which basically just means that the home page redirects to my Friendica profile page, rather than login/sign up. But that's not really want I wanted for my root domain name. I wanted it to point to my website. So how can I get Friendica to work with my static website? I don't think I can. But I <em>can</em> work around it...</p>
<h2>Cloudflare workers to serve them both</h2>
<p>So, as outlined above, I have my static website set up at <a href="https://www.death.id.au">https://www.death.id.au</a>, and my Friendica instance set up at <a href="https://death.id.au">https://death.id.au</a>. Cloudflare is already hosting my static website for free, and I can also set up &quot;workers&quot; for free as well. Workers is, in Cloudflare's own words, &quot;A serverless platform for building, deploying, and scaling apps across Cloudflare's global network...&quot;.</p>
<p>The upshot is, I have set up a worker that intercepts all requests to <a href="https://death.id.au">https://death.id.au</a>. I've got some basic javascript code to check against a list of regular expressions for the paths I've used in my static website and if it matches one of them, return the data from <a href="https://www.death.id.au">https://www.death.id.au</a> instead. This has potential to lead to conflicts; if a URL on my static website matches one on Friendica, it's going to prioritise my static site instead. But my site's pretty basic, I think it should be fine.</p>
<p>And now, like magic, <a href="https://death.id.au/">https://death.id.au/</a> will serve up my static website, <a href="https://death.id.au/now/">https://death.id.au/now/</a> should serve my now page, etc. etc. Meanwhile, <a href="https://death.id.au/profile/death.au">https://death.id.au/profile/death.au</a> will serve up my profile page on Friendica and, most importantly, any and all ActivityPub, webfinger, API calls, etc will be served on Friendica and my social handle of <code>@death.au@death.id.au</code> is all working fine. The best of both worlds!</p>
<h2>Omg.lol  - How does this fit in?</h2>
<p>A previous version of my website was built on the omg.lol platform. That place is great, and I plan to use it well into the future. But for me to have the control I wanted over this setup, I had to abandon weblog.lol, the blogging service provided by omg.lol. However, I am still heavily utilizing other aspects of the service. I've decided that most, if not all, of the images I'm using on my website are hosted at some.pics. I have plans and ideas to perhaps integrate the omg.lol provided profile and now pages into my website. They are, after all, managed by markdown. I just have to figure out how to trigger the data to be dumped into my github repository when the data changes.</p>
<p>But the biggest part I'm using at the moment is status.lol for microblogging. It's just simple and fun. You write a message (less than 500 characters), choose an emoji to display alongside it, and send it out. There's no likes or anything on the platform, but there is a checkbox to also post the status to Mastodon. As Friendica has Mastodon-compatible APIs, this works on Friendica, too. Status.lol also supports webhooks, meaning whenever I post a status, I can get this sent to an arbitrary URL.</p>
<p>So, I set up another Cloudflare Worker to accept the data from status.lol, format an appropriate markdown file and push that into my repository. That triggers a new build of the website and within seconds, that status is also visible on my website!</p>
<h2>The future</h2>
<p>I am very happy with how all this turned out. There's nothing I love more than tinkering, and the nature of this setup allows me endless possibilities for tinkering. I have Fediverse &quot;integration&quot; via some javascript I wrote which fetches interaction data on articles and posts from Friendica, and allows someone to &quot;log in&quot; with their ActivityPub handle, and if the script can obtain a valid sharing URL, provides the ability to link directly to posts and statuses on your own instance for replying, liking, boosting, etc. As mentioned earlier, I also want to integrate omg.lol's profile and now page functionality to make it easy to modify those pages through either omg.lol or my <a href="https://neighbourhood.omg.lol">neighbourhood.omg.lol</a> app without having to manually check out and push a git repository.</p>
<p>I probably want to look into a headless CMS in the future, but I can also manage my blog posts and pages through Obsidian, as they're all basically in Markdown anyway. I set up a synced vault in my content folder, so I can edit blog posts and pages in Obsidian, on my mobile or anywhere, and then when I'm back at my computer I can review, update some metadata and then push the markdown into my git repository to be published on my website.</p>
<p>Most importantly, I think this setup is pretty stable, and most importantly, fun to tinker with. My biggest hurdle is actually just thinking up things to write about in the first place 😅</p>
</div></content>
    <link rel="alternate" href="https://death.id.au/b4.0017/" />
    <summary type="html">So, I&#39;ve re-jiggered my website *again*, and moved away from Ghost, back to 11ty, with a Friendica instance along for the ride...</summary>
  </entry>
  
  <entry>
    <title>Sliding Panes (Andy Matuschak Mode)</title>
    <id>https://death.id.au/cv.05/</id>
    <updated>2020-10-27T00:00:00Z</updated>
    <published>2020-10-27T00:00:00Z</published>
    <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><h1>Sliding Panes (Andy Matuschak Mode)</h1>
<p>In early 2020, I found myself inspired to try and collect my thoughts in a
collection of linked notes. I was mostly inspired by <a href="https://notes.andymatuschak.org/">Andy Matuschak</a>
and the way his notes website opened linked notes in columns, so you could see
where you'd been.</p>
<p>I did attempt to create my own similar thing for a bit, but then I discovered
<a href="https://obsidian.md">Obsidian</a>. It was pretty new at the time, but seemed to do
what I wanted in terms of linking notes together.</p>
<p>However, it didn't have the cool horizontal stacked notes thing that Andy Matuschak's
website had. However, based on my experiments with building a similar thing myself,
I thought I could modify the internal CSS of Obsidian to present notes in a similar
format. And it worked! Not only that, it was <a href="https://forum.obsidian.md/t/170">pretty popular</a> with others!</p>
<p>Later in the year, the Obsidian team opened up their plugin API (which I helped
discuss with the devs) and I migrated my fun CSS tweak into a full-blown JavaScript
plugin. Doing so won me the inaugural <a href="https://obsidian.md/blog/2020-goty-winners/">Best Plugin</a>
of the year in 2020.</p>
<p>The functionality has been since incorporated into the core of Obsidian. By using
the &quot;stack tabs&quot; option, you get vertically stacked notes that slide over each other,
just like I'd envisioned from Andy Matuschak's website.</p>
</div></content>
    <link rel="alternate" href="https://death.id.au/cv.05/" />
    <summary type="html">A unique way to view open panes in Obsidian.</summary>
  </entry>
  
  <entry>
    <title>Shopfront Solutions</title>
    <id>https://death.id.au/cv.03/</id>
    <updated>2016-10-06T00:00:00Z</updated>
    <published>2016-10-06T00:00:00Z</published>
    <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><h1>Shopfront Solutions</h1>
<p>This was a big project.</p>
<p>It started small enough. A system to print shelf tickets for a group of pharmacies.
Of course, that had its own technical challenges. Not least of which was printing
from a browser in such a way that it would be consistent with pre-printed and/or
pre-perforated paper. We ended up developing an unobtrusive background application
for windows that would handle communication with the printer, and a separate web
system for creating the print jobs.</p>
<p>This expanded a lot. We built an entire visual editor for printible tickets with
multiple dynamic labels and layouts of those labels. We also expanded the management
side of the system to handle promotional periods and other forms of marketing.
The idea became for marketing departments to create and upload assets and product
pricing details, and the stores just had to log on and print what was relevant to
them.</p>
<p>With nearly a thousand stores using the system, there was a lot of iteration around
the UI/UX of the system, especially about making life easier for the store users.
We also started handling support tickets and calls (myself included) in order to
better assist the stores. This helped me put an emphesis on the UX design, as I
got to know the kinds of people that were using the system (and designing things
so I had less support tickets to attend do :P ).</p>
<p>Before I left, I was part of the management of the product as a whole, and was
working on a big redesign and re-branding. Partially in an attempt to simplify
the entire system, and to reach out to more non-pharmacy retail chains and outlets.</p>
</div></content>
    <link rel="alternate" href="https://death.id.au/cv.03/" />
    <summary type="html">A platform for digital marketing in the retail sector.</summary>
  </entry>
  
  <entry>
    <title>Pathogen Attack!</title>
    <id>https://death.id.au/cv.06/</id>
    <updated>2015-07-01T00:00:00Z</updated>
    <published>2015-07-01T00:00:00Z</published>
    <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><h1>Pathogen Attack!</h1>
<p>This was a javascript-based online game, built for the Gene Technology Access Centre
(<a href="https://gtac.edu.au/">GTAC</a>). I was mainly involved in the design phase, as opposed
to the development, but what a design phase it was!</p>
<p>The goal of this game was to help teach VCE level students about the human immune
system through an interactive game. Of course, this meant that the first step was to
learn all about the human immune system. Over a period of many weeks, there were many
discussions with the GTAC educators, alongside actual immunology experts, to get a
handle on which cells play a role in the immune system and their function.</p>
<p>There was a lot of back and forth, gaining knowledge, assembling it and then
confirming it with the experts. In the process, I think even the educators learned new
things.</p>
<p>Afterwards, we had to translate the cells and their processes into game mechanics —
attacking invading pathogens, recognising pathogens and utilizing the lymphatic nodes
to produce appropriate T-Cells, sending out different forms of Dendritic Cells and
acrophages... In the end it became an interesting (and hopefully entertaining)
real-time strategy game. And I learned a lot about the immune system in the process.</p>
</div></content>
    <link rel="alternate" href="https://death.id.au/cv.06/" />
    <summary type="html">An educational game for GTAC exploring the human immune system</summary>
  </entry>
  
  <entry>
    <title>Robotic Mission to Mars</title>
    <id>https://death.id.au/cv.01/</id>
    <updated>2013-05-12T00:00:00Z</updated>
    <published>2013-05-12T00:00:00Z</published>
    <content type="xhtml"><div xmlns="http://www.w3.org/1999/xhtml"><h1>Robotic Mission to Mars</h1>
<p>This was one of my favourite projects to work on, despite the frustrations.
A cloud-based application for the Victorian Space Science Education Center (VSSEC),
which would allow a class of secondary school children log in, be assigned various
mission roles and run a simulated Mars Rover mission.</p>
<p>The kicker: students actually ended up controlling a physical rover robot that would
drive around VSSEC's on-site Mars simulation area.</p>
<p>It was a technical challenge to run 12+ users concurrently viewing the same set of
data, and issuing requests and instructions to each other. Data was partially plotted
in advance, and partially randomised. There were also random events like dust storms
or electromagnetic interference, which the students would have to cooperate and
coordinate to overcome.</p>
<p>Plus the added technical challenge of interfacing with VSSEC's rover, as well as
computer vision data to track its physical location on the ground. It would've been
so much easier to simulate that data along with the rest, but you can't beat the
coolness factor of driving a real robot and seeing the live feed from its camera.</p>
<p>Also, once development was almost complete, we recieved graphic designs from a
design agency, which required a lot of re-working to the front-end to make it fit
the final design. It ended up looking great, though.</p>
</div></content>
    <link rel="alternate" href="https://death.id.au/cv.01/" />
    <summary type="html">An interactive educational game which simulates a Robotic Mission to Mars, including control of a real-life robot!</summary>
  </entry>

</feed>