Compare commits

..

3 commits

30 changed files with 321 additions and 1586 deletions

2
.gitignore vendored
View file

@ -189,3 +189,5 @@ Icon
Network Trash Folder
Temporary Items
.apdisk
/.quarto/

View file

@ -7,3 +7,6 @@ overrides:
- files: "*.gotmpl"
options:
parser: "go-template"
- files: "*.qmd"
options:
parser: "markdown"

View file

@ -14,7 +14,5 @@ steps:
image: codeberg.org/xfix/plugin-codeberg-pages-deploy:1
settings:
folder: public
git_config_name: Woodpecker CI
git_config_email: ci@millironx.com
ssh_key:
from_secret: ssh_key

View file

@ -9,5 +9,5 @@
}
}
},
"file_types": { "HTML": ["gotmpl"] }
"file_types": { "HTML": ["gotmpl"], "Markdown": ["qmd"] }
}

13
_quarto.yml Normal file
View file

@ -0,0 +1,13 @@
project:
type: hugo
preview:
serve:
cmd: "hugo server --port {port} --bind 0.0.0.0 --navigateToChanged"
ready: "Web Server is available at"
format:
hugo-md:
code-fold: true
execute:
warning: false

View file

@ -289,6 +289,15 @@ article > p {
line-height: 1.5em;
}
h2#references ~ p {
font-size: 1rem;
text-justify: unset;
text-align: left;
hyphens: none;
line-height: 1rem;
text-indent: 2ch hanging;
}
article h1 + p,
article h2 + p,
article h3 + p,
@ -337,6 +346,10 @@ figure:has(blockquote) > figcaption {
font-size: 80%;
}
figure > img {
max-width: 100%;
}
footer {
display: flex;
flex-direction: column;

179
assets/styles/mix-twbs.scss Normal file
View file

@ -0,0 +1,179 @@
$web-font-path: "/fonts/nunito-sans/index.css";
@import "bootstrap/functions";
@import "bootswatch/lux/variables";
@import "bootstrap/bootstrap";
@import "bootswatch/lux/bootswatch";
$peek-height: 25rem;
.blurred-container {
position: relative;
width: 100%;
min-height: 35vh;
height: $peek-height;
top: 0;
left: 0;
}
.blurred-container .motto {
color: #fff;
font-size: 76px;
font-weight: 600;
text-align: center;
text-shadow: 0 0 10px rgba(0, 0, 0, 0.33);
text-transform: uppercase;
top: 7.5vh;
z-index: 3;
display: block;
margin: 0 auto;
position: relative;
width: 60%;
height: 0;
}
.motto h1 {
background: #0000007d;
-webkit-text-stroke: 0.3px $gray-100;
line-height: 1;
}
.blurred-container .img-src {
position: fixed;
width: 100%;
min-height: 35vh;
height: $peek-height;
background-repeat: no-repeat;
background-size: cover;
background-position: center center;
}
.list-main {
position: relative;
background-color: #fff;
padding-bottom: calc(1rem + 95px);
padding-top: 1rem;
}
.footer-contents {
position: relative;
background-color: $gray-200;
}
/* Gives me greater control over fonts */
.font-serif {
font-family: Georgia, "Times New Roman", Times, serif;
}
.font-sans {
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.font-mono {
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
}
.font-small-caps {
font-variant-caps: small-caps;
}
.strikethrough {
text-decoration: line-through;
}
.sfTable {
margin: 0 auto;
}
.text-align-left {
text-align: left;
}
.text-align-right {
text-align: right;
}
.float-left {
float: left;
}
.float-right {
float: right;
}
.media-object {
max-height: 100%;
max-width: 100%;
}
@media (min-width: 768px) {
.pull-left {
max-width: 25%;
}
.w-md-50 {
width: 50%;
}
}
h1 small {
font-size: 60%;
color: #9a9a9a;
font-weight: 300;
line-height: 1.5;
}
h1 {
font-weight: 400;
margin: 30px 0 15px;
}
.dogear {
clip-path: polygon(0 0, 0 100%, 100% 100%, 100% 25%, 75% 0);
}
.fa-stack-top {
vertical-align: top;
}
.table-condensed th {
padding: 0.5rem;
}
.table-condensed td {
padding: 0.5rem;
}
@media (min-width: map-get($grid-breakpoints, md)) {
.fixed-bottom {
left: 25%;
}
.md-max-width-33 {
max-width: 33%;
}
}
.card-link {
white-space: nowrap;
}
.category-button {
position: absolute;
top: 1em;
right: 1em;
display: grid;
}
.card-title {
margin-right: 2.25em;
}
.thumb-icon-wrapper {
display: flex;
justify-content: center;
}
.thumb-icon-badge {
font-size: xx-large;
}

View file

@ -1,6 +1,7 @@
baseURL = "https://millironx.com"
languageCode = "en-us"
title = "Milliron X"
ignoreFiles = ["\\.qmd$", "\\.ipynb$", "\\.py$"]
[pagination]
pagerSize = 5
@ -51,6 +52,9 @@ notAlternative = "true"
[markup.goldmark.parser.attribute]
block = true
[markup.goldmark.renderer]
unsafe = true
[markup.goldmark.renderHooks]
[markup.goldmark.renderHooks.link]

View file

@ -106,8 +106,8 @@ couldn't tell by the pictures). I've worn a lot of different hats:
| | |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| First and foremost | ✝️ {{< abbr "Born again, fundamentalist, dispensationalist, King James Bible-believing, independent Baptist" >}}Christian{{< /abbr >}} |
| Location | 🏇 {{< abbr "I want to keep claiming Wyoming, but they're pretty strict about residency there, and I don't want to repeat the Liz Cheney fishing license incident" >}}Wyomingite{{< /abbr >}} sojourning in 🦬 {{< abbr "Yah, youbetcha!" >}}North Dakota{{< /abbr >}} (this ought to be interesting) |
| Location | 🦬 {{< abbr "I want to keep claiming Wyoming, but they're pretty strict about residency there, and I don't want to repeat the Liz Cheney fishing license incident" >}}Wyomingite{{< /abbr >}} sojourning in 🌻 {{< abbr "Yes, Toto, we are in Kansas now" >}}Kansas{{< /abbr >}} (this ought to be interesting) |
| Education | 📐 {{< abbr "noun. A person who cannot build cool things like airplanes or bridges, but can blow up an entire neighborhood and kill dozens of children by misplacing a decimal point. See also: self-hating chemist" >}}Chemical Engineer{{< /abbr >}} |
| Profession | 🧬 {{< abbr "I do genetics and computers" >}}Bioinformatician{{< /abbr >}} and 🐄🩺 {{< abbr "I can't rope well enough to be a cowboy, so being a cow doctor will have to suffice" >}}Veterinarian{{< /abbr >}} |
| Profession | 🧬 {{< abbr "I do genetics and computers" >}}Bioinformatician{{< /abbr >}} and 🐄🩺 {{< abbr "I can't rope well enough to be a cowboy, so being a cow doctor will have to suffice" >}}Veterinarian{{< /abbr >}} (soon!) |
| Politics | 🗽 {{< abbr "Refers to the belief in natural social and political laws created by God that are as inflexible as the laws of physics" >}}Juris naturalist{{< /abbr >}} |
| Hobbies | 🎥 {{< abbr "Storytelling at 24 frames per second" >}}Filmmaker{{< /abbr >}} and 💃🏻 {{< abbr "Square dancing is more musically rigid and is standardized across the country. Swing dancing is more freeform and differs regionally. It's like the difference between classical music and jazz. I like both." >}}Square/swing dancer{{< /abbr >}} |

View file

@ -0,0 +1 @@
index.md

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View file

@ -0,0 +1,88 @@
---
title: I gave up Github (and more)
subtitle: Starting on a self-hosted blog
date: 2025-11-01
tags:
- blog
featured: true
categories:
- blog
---
If you are reading this post, then I have migrated most of my online services
over to a VPS to the point where I feel confident enough to host a blog on them.
Yay for me! It has been my goal for nearly six years now to be able to have an
_interesting_ corner of the internet, filled with interconnected links that try
to tell the story of what I've done, who I've done it with, and where to find
it, and this is the result.
I wanted to take the first post and indulge myself in explaining my motivation
for going self-hosted, show off my tech stack just a little bit, and lay out
some ground rules for myself for the posts ahead.
## My motivation
Microsoft is evil. There you have it. End of story. Thank you for coming to my
TED Talk.
![The dynamic duo of Microsoft roleplaying as supervillains](./ballmergatesaustinpowers.jpg)
Okay, but in all seriousness, the changes after Github was acquired by Microsoft
were annoying, and showed that Microsoft was prioritizing the wrong things, and
going back to their old ways of
[embrace, extend, exterminate](https://en.wikipedia.org/wiki/Embrace,_extend,_and_extinguish).
1. [A page redesign for phones](https://github.com/Kira-NT/GitHub-Defreshed)
2. An "Achievements" system that gamified risky behavior
3. An integrated CI/CD pipeline system that is
[terribly designed](https://youtu.be/9qljpi5jiMQ) and
[way too easy to use insecurely](https://docs.zizmor.sh/)[^*]
4. Finally, ahem, ARTIFICIAL INTELLIGENCE, with illegal scraping of repos and
reproduction of code without proper license compliance
I got into programming using Visual Basic .NET, writing automatic Punnet square
solvers and gestation calculators with Windows Forms, and designing websites
using ASP.NET MVC with Razor pages. Programming with Visual Basic was fun! At
least it was until Microsoft put
[someone who'd rather talk about her hip replacement than Visual Basic](https://www.dotnetrocks.com/details/1691)
in charge of development. When I started to see the same attitudes creeping
into Github, Github Actions, and Visual Studio Code, I made the decision to
move.
The fact is, if you are the kind of person who I expect to be reading this blog,
then you probably know more of the drama around Github than I do. That, or
you're the kind of person who is not affected by Github whatsoever. Either way,
I'll leave you with a collection of links I've found to be varying levels of
insightful on the whole subject, and leave it at that.
### Microsoft's behavior
- [Microsoft, there is a way to win our trust](https://davelane.nz/microsoft-there-way-win-our-trust)
- [Can we trust Microsoft with open source?](https://dusted.codes/can-we-trust-microsoft-with-open-source)
- [Visual Studio Code is designed to fracture](https://ghuntley.com/fracture/)
### Github-specific complaints
- [Give Up Github Campaign - Software Freedom Conservancy](https://sfconservancy.org/GiveUpGitHub/)
- [Why not Github?](https://sanctum.geek.nz/why-not-github.html)
- [We need to talk about your Github addiction](https://ploum.net/2023-02-22-leaving-github.html)
- [Why I won't use Github for any new projects](https://raccoon.onyxbits.de/blog/why-not-use-github/)
### Github Actions
- [Github Actions feels bad](https://youtu.be/9qljpi5jiMQ)
- [The pain that is Github Actions](https://www.feldera.com/blog/the-pain-that-is-github-actions)
- [Github Actions could be so much better](https://blog.yossarian.net/2023/09/22/GitHub-Actions-could-be-so-much-better)z
- [Github Actions are a problem](https://felix-knorr.net/posts/2023-11-11-github-actions.html)
### The problem in general
- [Free software hasn't won](https://dorotac.eu/posts/fosswon/)
[^*]:
Lumping Github Actions here might seem strange, but for context, I wrote and
maintained the
[nf-core/setup-nextflow](https://github.com/nf-core/setup-nextflow) Github
Action. Trying to make something coherent out of the Github Actions
ecosystem was harrowing, and convinced me more than anything else that
Github is just part of the embrace, extend, exterminate strategy.

8
flake.lock generated
View file

@ -20,16 +20,16 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1737597992,
"narHash": "sha256-FECKBxkd+w5I/fhsquthDiw/r/MdCpqmKikBU9yQGug=",
"lastModified": 1762756533,
"narHash": "sha256-HiRDeUOD1VLklHeOmaKDzf+8Hb7vSWPVFcWwaTrpm+U=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "d0bb4699177f691c8e558b32b3bdc38bc112f76f",
"rev": "c2448301fb856e351aab33e64c33a3fc8bcf637d",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable-small",
"ref": "nixos-25.05",
"repo": "nixpkgs",
"type": "github"
}

View file

@ -1,13 +1,13 @@
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable-small";
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
buildInputs = with pkgs; [ hugo nodejs ];
buildInputs = with pkgs; [ hugo nodejs quarto julia-bin ];
in with pkgs; {
devShells.default = mkShell { inherit buildInputs; };
packages.default = stdenvNoCC.mkDerivation {

View file

@ -1,19 +1,6 @@
{{ define "extraCss" }}
{{ $scrollCss := resources.Get "styles/scrolling-header.css" | minify }}
<link href="{{ $scrollCss.Permalink }}" rel="stylesheet" />
{{ range where $.Site.Pages "Title" "Thomas A. Christensen II" }}
{{ with .Params.accounts }}
{{ range $account, $username := . }}
{{ partial "account.html" (dict
"targetType" "link"
"account" $account
"username" $username
"isMe" true)
}}
{{ end }}
{{ end }}
{{ end }}
{{ end }}
{{ define "main" }}

View file

@ -39,14 +39,10 @@
{{ $userURL := print $urlBase .username }}
{{ if eq .targetType "a" }}
<a
href="{{ $userURL }}"
{{ if .isMe }}rel="me"{{ end }}
title="{{ .account | humanize }}: {{ .username }}"
>{{ partial "fa.html" $icon }}</a
>
{{ end }}
{{ if and (eq .targetType "link") .isMe }}
<link href="{{ $userURL }}" rel="me" />
{{ end }}
<a
href="{{ $userURL }}"
{{ if .isMe }}rel="me"{{ end }}
title="{{ .account | humanize }}: {{ .username }}"
>{{ partial "fa.html" $icon }}</a
>

View file

@ -1,6 +1,6 @@
{{ $thumbnailImage := .Resources.GetMatch "*thumbnail*" }}
{{ with $thumbnailImage }}
{{ $croppedImage := $thumbnailImage.Fill "960x320 center" }}
{{ $croppedImage := $thumbnailImage.Fill "1500x500 center" }}
<style>
.motto::before {
background-image: url('{{ $croppedImage.Permalink }}');

View file

@ -47,12 +47,7 @@
<div class="account-list">
{{ $isMe := eq $.Title "Thomas A. Christensen II" }}
{{ range $account, $username := . }}
{{ partial "account.html" (dict
"targetType" "a"
"account" $account
"username" $username
"isMe" $isMe)
}}
{{ partial "account.html" (dict "account" $account "username" $username "isMe" $isMe) }}
{{ end }}
</div>
</div>

View file

@ -1,51 +0,0 @@
{
"name": "Veterinary Drug Calculator",
"id": "/drug-calc.html",
"short_name": "Drug Calc",
"description": "Mobile drug dosage calculator for injectable and oral medications",
"start_url": "/drug-calc.html",
"display": "standalone",
"orientation": "portrait-primary",
"theme_color": "#198754",
"background_color": "#e8e8e8",
"icons": [
{
"src": "/icons/drug-calc-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/drug-calc-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icons/drug-calc-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/icons/drug-calc-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"screenshots": [
{
"src": "screenshots/drug-calc.png",
"sizes": "750x1334",
"type": "image/png"
},
{
"src": "screenshots/drug-calc-wide.png",
"sizes": "1280x800",
"type": "image/png",
"form_factor": "wide"
}
],
"categories": ["medical", "utilities"]
}

View file

@ -1,92 +0,0 @@
// SPDX-License-Identifier: WTFNMFPL
"use strict";
/**
* drug-calc service worker
*
* Strategy:
* - HTML/manifest (the files that actually change release to release):
* network-first, falling back to cache only when offline. This means
* edits show up on next load automatically, with no version bump needed.
* - Icons + CDN libs (rarely change): cache-first, for offline/speed.
*
* CACHE_NAME still gets bumped on release so any previously-cached (stale)
* entries — e.g. from before this network-first change shipped — are
* dropped on activate.
*/
const CACHE_NAME = "drug-calc-v1.1.1";
const PRECACHE = [
"/drug-calc.html",
"/drug-calc-manifest.json",
"/icons/drug-calc-192.png",
"/icons/drug-calc-512.png",
"/icons/drug-calc-apple.png",
// CDN resources — cached so the app works fully offline after first visit
"https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css",
"https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js",
];
// Assets that should always be freshened from the network when possible.
const NETWORK_FIRST = ["/drug-calc.html", "/drug-calc-manifest.json"];
/* ── Install: cache every precache URL ─────────────────────── */
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE))
);
// Activate immediately — don't wait for old tabs to close
self.skipWaiting();
});
/* ── Activate: delete stale caches ────────────────────────── */
self.addEventListener("activate", (event) => {
event.waitUntil(
caches
.keys()
.then((keys) =>
Promise.all(
keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k))
)
)
);
// Take control of already-open clients
self.clients.claim();
});
/* ── Fetch ──────────────────────────────────────────────────
* - NETWORK_FIRST assets: try the network so updates are seen
* immediately; fall back to cache when offline.
* - Everything else we own (icons + CDN): cache-first.
* ------------------------------------------------------------ */
self.addEventListener("fetch", (event) => {
const url = new URL(event.request.url);
// Only intercept requests for drug-calc assets (local + CDN)
const isDrugCalcPage =
url.pathname === "/drug-calc.html" ||
url.pathname === "/drug-calc-manifest.json" ||
url.pathname.startsWith("/icons/drug-calc-");
const isCDN = url.host === "cdn.jsdelivr.net";
if (!isDrugCalcPage && !isCDN) return; // let everything else pass through normally
if (NETWORK_FIRST.includes(url.pathname)) {
event.respondWith(
fetch(event.request)
.then((fresh) => {
caches
.open(CACHE_NAME)
.then((cache) => cache.put(event.request, fresh.clone()));
return fresh;
})
.catch(() => caches.match(event.request))
);
return;
}
event.respondWith(
caches.match(event.request).then((cached) => cached || fetch(event.request))
);
});

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 581 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 970 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 972 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB