The stack
C#
ASP.NET Core 8 MVC
Razor
Blazor WebAssembly
Entity Framework Core
SQLite
ASP.NET Core Identity
Azure App Service
JavaScript
SVG & CSS animation
Bootstrap
- Web app: ASP.NET Core 8 MVC with Razor views, a Bootstrap base and a custom space theme
built on CSS variables, with self-hosted fonts.
- Moon Journal: a separate Blazor WebAssembly app, hosted by the
main site at
/journal/. It installs like a native app, works offline and keeps entries on
your device.
- Data: Entity Framework Core with SQLite for Moon Pulse, sign-in accounts (ASP.NET Core
Identity with roles, no public sign-up) and the Band Name Generator's list of names already taken. On
Azure the database files live in App Service's persistent storage, so they survive redeploys.
- Outside services: MusicBrainz for band names (cached, and identified as their rules
ask) and Open-Meteo for the birth chart's city search. Public endpoints are rate-limited with ASP.NET
Core's built-in limiter.
- Astronomy: the Sun, Moon and planets come from Astronomy Engine (MIT-licensed). Chiron
and four asteroids come from tables downloaded from NASA JPL's Horizons system (1799–2051), interpolated
between stored days.
Getting the sky right
I check the calculations against NASA JPL's Horizons system,
write the results down, and the pages quote them. The app has small command-line modes that print its own
results (for example dotnet run -- list-ingresses 2026 2028), and PowerShell scripts fetch
JPL's answers for the same moments and compare the two.
- Sign changes and retrogrades: for 1960–61, 2023–24, 2026–27 and 2046–47, every sign
change JPL lists was found and nothing extra. All are within an hour except one (3 hours), and all 54
retrograde ends are within 2 hours (JPL gives them to the nearest hour).
- Birth chart: across 1800–2050 the Sun is within 0.53′ of JPL and the Moon within 0.87′
(a sign is 1,800′). The Ascendant is within 1.62′ and the Midheaven within 0.30′ of an independent
calculation, since JPL doesn't compute those.
- Chiron and the asteroids: identical to fresh JPL queries to 0.0001°.
- Calendar: solstices and equinoxes within 0.1 minute; Western Easter for all 201 years
from 1900 to 2100 matches the Royal Observatory of Belgium's table.
The checking has changed the code more than once. The site started with a simpler orbital model; comparing
it with JPL showed Saturn up to 47 hours off and Mars's two-day dip into Gemini in February 1961 missed
entirely, so I replaced it. The calendar's solstices were up to 10 minutes off with a shorter Sun formula;
they're now within 0.1 minute.
Accessibility
- Motion: animations (the stars, the seasonal tree, the floating decorations) have a
prefers-reduced-motion fallback that keeps them still.
- Pictures that carry information have a text version: the
Wheel of the Sky is described for screen readers and
has a full table of positions beside it, and the tree's season is announced in a live caption.
- Keyboard: the calendar's months
are tabs you can move through with the arrow keys.
- Subtitles: silent films with German or Danish title cards have English subtitles
(WebVTT), written from the original cards.
- Colour: colours used for text are checked for contrast against the dark background.
- Practice: I wrote a 24-exercise accessibility course, each exercise tied to WCAG 2.2
success criteria, for the site's members-only practice area.
Privacy
- Moon Pulse stores only a date, a
mood and an energy level: no IDs, notes, times or IP addresses. Its spam limit holds IP addresses in
memory only and never writes them down.
- Moon Journal entries stay on your device. Sharing a day's mood with Moon Pulse is
opt-in, and only yesterday's last entry is shared, once.
- Birth chart details are used only to draw the chart; they aren't saved anywhere.
Engineering details
A slider that feels instant
Dragging the Wheel of the Sky's year slider moves the
Sun, Earth, the planets, the Moon's phase and the tree straight away. The first drag fetches a year of daily
positions once (about 48 KB, which the browser keeps for a day), and the page moves everything from that.
When you pause for 140 ms, the server's exact wheel for that date replaces the preview, and an
AbortController cancels any request a newer one has overtaken.
Placing crowded planets
Planets bunch up in the sky. The wheel lays crowded bodies out in groups, each spread evenly around its
average position and merged with the next group if they touch, and bodies at clearly different distances
from the Sun are allowed to sit side by side. It replaced a simpler push-apart approach that could leave two
bodies on the same spot.
Code exercises that run in the browser
The practice area runs learners' code without a server: Python in a Web Worker with Pyodide, TypeScript
type-checked by the real compiler in a worker, and SVG and HTML in sandboxed iframes that can't run
scripts. The REST and SOAP course talks to a pretend API server that lives inside the worker. A self-test
page runs every exercise's solution (each must pass) and its starter code (each must fail).
Content rules
- No AI-generated images. Every picture is either public domain (mostly from Wikimedia
Commons and library collections, credited on the page) or drawn in code with SVG and CSS.
- Films are public-domain copies, checked for added modern music and newer
restorations, which can carry their own copyright.
- Facts come from primary texts first, then academic sources; Wikipedia is only a
pointer to its sources. Each section keeps a list of its claims and where they come from.
See the results on the Projects page, or
get in touch.