Behind the scenes
About this site
What it’s built with, where it runs, and how the sky up top knows what time it is.
The spec sheet
- Generator
- Astro v7.4.0, fully static
- Language
- TypeScript, strict
- Styles
- Hand-written CSS on design tokens. No framework.
- JavaScript
- A handful of small modules. No framework.
- Type
- Newsreader, Instrument Sans, JetBrains Mono, self-hosted
- Web server
- Caddy, in a container on the Docker host
- Deploys
- GitHub Actions builds, Komodo deploys
- Front door
- Pangolin on a VPS, tunnelled home over WireGuard
- Source
- GitHub
- Analytics
- Visit counts with Umami, self-hosted, reached through this site
- Cookies
- None
- Live data
- NWS, NOAA, AIS and GitHub, fetched server-side
- Third parties
- None. Every request your browser makes goes to this site.
- Sky right now
- Computed in your browser (needs JavaScript)
Where it runs
Everything here is plain files: HTML, CSS, fonts and a few scripts. There’s no database, no app server and nothing running per request but the web server itself. Here’s the trip your browser just made:
- Pangolinon a small rented VPS. Terminates TLS, then sends the request down a WireGuard tunnel to my homelab in Minnesota.
- Caddyin a container on the Docker host. Serves the files, adds the security headers and compresses with zstd or gzip.
- FilesHashed assets are cached for a year; the HTML is revalidated every time, so a deploy shows up right away.
A release is a git tag. GitHub Actions builds fresh images (the whole site, checked on the way) and publishes them; Komodo pulls the new one and swaps the container. Every release keeps its own tag, so rolling back is pointing at an older one.
One site, four sections
The home page, the how-tos, projects and the resume are one Astro build. Each section keeps its pages in its own folder, and a small integration mounts them under their paths, so they share the nav, footer, design system and one sitemap. How-tos are Markdown in git; git is the CMS.
The sky is live
The band at the top isn’t a picture of St. Paul’s sky. It’s a calculation of it. Your browser works out the sun’s altitude over 44.95°N using the NOAA low-precision solar formulas (good to about half a degree) and picks night, dawn, day, golden hour or dusk. The date picks the season: snow in winter, drizzle in spring, fireflies in summer, and a quiet sky in fall. The moon is tonight’s actual phase, glade and all.
After dark there’s an aurora, drawn by a WebGL fragment shader at half resolution, capped at 30 fps and paused when the tab is hidden. If you’d rather things held still, it draws one frame and stops. None of it touches the network. Can’t wait for nightfall? Try a winter night or a summer dusk.
The readings under “Minnesota right now” on the home page are real, though. A small service next to the web server asks the National Weather Service about St. Paul, NOAA about the water at Duluth and the space weather forecast for tonight’s Kp, and listens to ships’ AIS broadcasts on Lake Superior for the next one bound for the bridge. It writes one JSON file every 15 minutes; your browser only ever fetches that file from this site. The aurora in the header stays art: it doesn’t follow the forecast.
The terrain is real
The faint contour lines are real elevation: USGS 3DEP data from public terrain tiles, traced into contours at build time with d3-contour. The home page has the Mesabi Range on the Iron Range, where I grew up. Page headers have the Mississippi bluffs in St. Paul, and the footer has Duluth’s hillside stepping down to Lake Superior.
The lake, the far shore and the spruces are generated too, by a seeded script, so it’s the same trees every build. In winter the lake freezes over.
Locked down on purpose
Every page is served under this Content Security Policy:
default-src 'self';
script-src 'self';
connect-src 'self';
style-src 'self';
img-src 'self' data:;
font-src 'self';
object-src 'none'; base-uri 'self';
frame-ancestors 'none'; form-action 'self'That means no inline scripts or styles at all, and the build fails if one sneaks in. Another check fails the build if a private IP address or internal hostname shows up anywhere in the output. My email address isn’t in the HTML as text; a script puts it together after the page loads, which keeps the lazier scrapers out. There are no cookies, so there’s no banner to click through.
Privacy
Short version: I count visits; I don’t track you.
- No cookies, and nothing is stored in your browser.
- I count visits with Umami, running on my own server. It keeps which pages are read, the site that linked here, your browser, operating system, device type, screen size and language, and your country and region. Not your IP address, not your city, and not anything after a
?or#in the address. Each visit gets an ID that changes every day, so nobody can follow a visitor from one day to the next. Counts are deleted after 13 months. If your browser sends Do Not Track, nothing is counted. - This site’s web server keeps no access logs. The proxy in front of it, which handles HTTPS, may log connections for security; those logs aren’t used to count visitors.
- The live readings on the home page and the projects list are fetched by my server, not your browser: your browser only ever talks to this site. Nothing is shared or sold, and there are no ad networks or third-party scripts.
Budgets
- CSS under 30 KB per page, before compression (about 8.5 KB gzipped). The build fails if a page goes over.
- Fonts under 200 KB, and the generated textures under 200 KB, also checked on every build.
- Zero axe violations at WCAG 2.2 AA, on phone and desktop widths.
- Text contrast measured against the brightest frame of the aurora: at least 5.2:1, and 10:1 for most of it.
- The menu’s see-through band keeps its links readable over anything that scrolls under it: each link carries a soft dark halo, so contrast never depends on what’s behind the glass.
Built October 24, 2026 from b8fd422 with Astro v7.4.0 on Node v24.22.0.