I wanted my blog to feel like a terminal — not in the lazy "monospace font and a green-on-black palette" way, but structurally. The model I kept coming back to was neofetch: an ASCII distro logo on the left, a tidy key/value table of system info on the right. That layout is a homepage. So I built one.
The result is Consoler Dark, the Zola theme this site runs on. This is how it works, including the two parts I'm fondest of: a script that generates logos by scraping neofetch itself, and a database that runs entirely in your browser.
Starting from after-dark
I didn't start from scratch. Consoler Dark is a fork of comfusion's after-dark, a minimal Zola theme built on top of hack.css. You can still see hack.css's fingerprints in the SCSS — the .hack utility classes, the no-nonsense defaults. After-dark gave me a clean skeleton; I layered the terminal identity on top.
The taxonomy setup is where I diverged most. Beyond the usual categories and tags, the site indexes series, projects, and authors — that last one so I can credit the AI models I draft alongside as co-authors (this post included).
The neofetch metaphor, in templates
The homepage and section headers render a .neofetch-header: a logo block next to a tech_info table driven straight from config.
[extra.tech_info]
Title = "parkerjones.dev"
Framework = "Zola"
Theme = "Consoler Dark"
Hosting = "Github Pages"
The template loops over those key/value pairs the way neofetch prints OS, Kernel, Uptime. The nice property is that any page can override individual fields via page.extra.tech_info, so a project page can advertise its own "system specs" while inheriting the site defaults. It's config-as-content, and it keeps the homepage honest — the chrome describes the actual stack.
A few smaller touches round out the feel: an intro keyframe fade so content boots in rather than flashing, a 16:9 responsive-iframe helper for embeds, and :target highlighting so deep links land with a visible cursor-style focus.
@keyframes intro { 0% { opacity: 0; } 100% { opacity: 1; } }
main, footer { animation: intro 0.3s both; animation-delay: 0.15s; }
Generating logos by scraping neofetch
Here's the part I enjoyed most. Neofetch ships ASCII art for hundreds of distros, embedded right in its shell script as heredoc blocks. Rather than hand-draw anything, I wrote a Python script that fetches the raw neofetch source, parses out every logo, and converts each to an SVG.
The parser keys off neofetch's own structure — each logo is a read -rd '' ascii_data <<'EOF' … EOF block, and the distro name is in the case line just above it:
if "read -rd '' ascii_data <<'EOF'" in line:
# walk backwards to the case label, e.g. "Ubuntu"|"ubuntu")
j = i - 1
while j >= 0 and not re.search(r'\)$', lines[j].strip()):
j -= 1
distro_name = re.findall(r'"([^"]+)"', lines[j])[0]
# collect everything up to the EOF sentinel
ascii_block = []
i += 1
while lines[i].strip() != "EOF":
ascii_block.append(lines[i]); i += 1
Converting ASCII to SVG is just laying each line down as a <tspan> in a monospaced <text> element, sized from the longest line:
char_width, line_height, font_size = 8, 18, 14
width = max(len(l) for l in lines) * char_width + 10
height = len(lines) * line_height + 10
# one <text> with a <tspan dy="line_height"> per line
Run it, and static/logos/ fills up with a clean SVG per distro — vector, themeable, no image assets to hand-maintain. When neofetch adds a logo, I re-run the script. The art stays in sync with upstream because upstream is the source of truth. That's the kind of automation that pays for itself the first time you'd otherwise have copied ASCII art by hand.
A database that runs in your browser
The detail that surprises people: Consoler Dark ships a WebAssembly build of SurrealDB and runs it client-side. There's no backend. The theme's js/main.js boots an in-browser SurrealDB engine, fetches a /site.sql file, pulls the SQL out of a <pre> tag, and executes it — all in the visitor's tab.
import { Surreal } from "surrealdb";
import { surrealdbWasmEngines } from "@surrealdb/wasm";
const db = new Surreal({ engines: surrealdbWasmEngines() });
const sql = await fetchSQLFromPreTag(); // read /site.sql from a <pre>
This is bundled with Vite. A couple of non-obvious settings were required to make a wasm database happy in a static-site context: an es2020 target plus top-level-await support (the engine initializes asynchronously at import time), and excluding @surrealdb/wasm from dependency pre-bundling so Vite doesn't mangle the wasm.
build: { target: 'es2020', outDir: 'static' },
esbuild: { target: 'es2020', supported: { 'top-level-await': true } },
optimizeDeps: { exclude: ['@surrealdb/wasm'] },
Why do this on a blog? Because I write about databases in the browser and using SurrealDB as a site cache, and a theme that is the demo beats one that merely links to it. The dev workflow runs Zola and Vite in parallel so the static site and the wasm bundle rebuild together:
"dev": "npm-run-all --parallel vite-serve zola-serve"
The linkify experiment I rolled back
Not everything survived. I'd wired up a client-side linkify pass that auto-detected #hashtags in post text and rewrote them into links to the matching /tags/ page — Twitter-style tagging, for free, with no changes to how I wrote posts.
formatHref: (href, type) =>
type === 'hashtag' ? `/tags/${href.slice(1).toLowerCase()}` : href
It worked, and I turned it off. Scanning every <p> and <span> on load and rewriting innerHTML is a lot of DOM churn for a cosmetic feature, it fought with code blocks that legitimately contain #, and "clever text rewriting at runtime" is exactly the kind of thing that quietly breaks a year later. The code's still in the repo behind a partial; the lesson is that shipping and keeping are different decisions, and a static site rewards restraint.
Takeaways for rolling your own Zola theme
- Fork something minimal. after-dark + hack.css gave me a correct baseline so I could spend my effort on identity, not resets.
- Drive chrome from config. The
tech_infotable means the design describes the real stack and updates itself. - Automate your assets from their upstream source. The neofetch scraper means I never hand-edit ASCII art.
- A static site can do more than you think. A wasm database in the client turned my theme into a live demo of the things I write about.
- Be willing to delete features that work. Linkify was the most "impressive" thing I built and the right call was to switch it off.
The theme is open source under MIT. If you want a blog that boots like a terminal and ships a database to every reader, the pieces are all here.
— Parker Jones, parkerjones.dev