I Rebuilt My Site as a Procedural ASCII Cathedral

2026-09-09 · 4 min read

My old homepage was an interactive 3D room. It was fun to build, heavy to load, and after a year it felt like someone else’s website. I wanted something closer to how I actually spend my time: a terminal, monospace text, black and white.

So I rebuilt the whole site around one idea — a gothic cathedral, drawn in ASCII, generated in the browser.

The look

The references were dense character-art pieces: portraits and textures rendered in .,:;*/(#%&@, heavy inked masses dissolving into black, and a back-piece tattoo of a cathedral sketched in loose, unfinished linework. The design language that fell out of that:

  • Pure black ground, bone-white ink, one monospace font for everything.
  • No color anywhere except muted green/red where data genuinely needs it (P&L on the trading dashboard).
  • Hover states invert — white block, black text — like a terminal cursor.
  • The navigation is a JSON object with vim-style relative line numbers.

Drawing a cathedral with noise

The cathedral isn’t an image. It’s generated at page load by a few hundred lines of vanilla JS and rendered into a <pre>.

The skeleton is a height profile across the viewport: seven “steeples,” each defined by three numbers — center position, half-width, and height. Each contributes a tapered needle cap, an upper tower, and a buttressed skirt, and the profile takes the max wherever they overlap. The main spire peaks at exactly 75% of the viewport width, which meant sizing the character grid off measured glyph dimensions rather than assuming — my first version capped the grid at 260 columns, and on a wide monitor the whole cathedral quietly slid left.

The fill is where it gets fun. For every character cell below the silhouette:

  • Two octaves-stacked value-noise fields — one isotropic, one stretched vertically — blend with depth to pick a character from a light-to-dark ramp: ' . : ; ! / \ | ( # % & @.
  • A low-frequency “gash” field carves blank streaks through the body, so it reads as unfinished ink sketch instead of a filled shape.
  • Columns on a loose rhythm get darkened into pointed-window masses.
  • Every ninth-ish row becomes a = ledge line, which gives the towers their stonework banding.

Silhouette edges get crisp / and \ strokes, single-column needles become |, and spires above a height threshold get a + cross on top. The same deterministic hash drives everything, so the cathedral is identical on every load — it’s a drawing, not static.

The details that made it feel finished

A JSON index with vim line numbers. The left nav renders as a JSON object — "projects": [ … ] — where each section unfolds as you scroll past it and folds shut behind you. The gutter runs :set relativenumber: the active section’s line shows its absolute number, everything else shows its distance. Folding a node renumbers the gutter, because of course it does.

Scroll-spy that survives short pages. A minimal homepage barely scrolls, which breaks every IntersectionObserver recipe — sections at the bottom never reach the trigger band. The fix: each section activates when its top crosses a reading line or when you’ve scrolled past its even share of the total scroll range, whichever comes first.

Light mode as a different material. Inverting the palette mechanically looked terrible — sterile white, invisible art. Light mode instead re-derives the whole thing as ink on warm paper: parchment ground, near-black warm ink, and the cathedral dropped to a graphite gray at 75% opacity, like a pencil study of the same building.

An OG image from the same generator. The social card runs the identical algorithm in Node, renders the grid into SVG text lines, and rasterizes it with sharp. One source of truth for the art.

What I kept

The terminal version of the site survived — it was already the most on-brand thing here. So did the trading and homelab dashboards, which inherited the monochrome palette through CSS custom properties without touching a component: the old theme’s tokens just got remapped, with green and red kept for the numbers that need a sign.

Everything else — the 3D room, the “traditional” portfolio page, two stale resume PDFs — got deleted. The whole site is hand-written HTML and CSS with no tracking, and the homepage’s only JavaScript draws a cathedral and counts lines like vim.

#design #javascript #ascii #astro

← all posts