The source for my personal site: a blog, CTF writeups, and a homepage covering what I work on. It started as a fork of the astro-theme-vitesse template and has drifted a long way from it since.
I forked the theme because the original had stopped getting updates. I keep it on current dependencies and add features when I need them. If any of it is useful to you, take it.
- Astro 7 — static site generation, content collections via the content layer API
- Vue 3 — the few interactive islands (charts, radar)
- UnoCSS — atomic CSS, dark theme only
- MDX — posts and writeups
- Bun — package manager and runtime
Built on top of the original Vitesse theme:
- 6 content collections —
blog,infosec,ctf,musings,research,pages - Auto OG images — a unique Open Graph image per page and per post, rendered with Satori
- View and like counters — serverless, talking straight to the Firestore REST API with no SDK and no API key
- Password-protected posts — bodies encrypted at build time, unlocked in the browser (see below)
- Share dialog on every post, with a copy-link fallback that works on Android
- Build freshness stamp — the footer shows the deployed commit and checks it against GitHub
- Self-hosted variable fonts — Google Sans Flex and Google Sans Code, so no external font requests
- Competition record and CTF contributions — tables of placements and challenge authoring on the homepage
- Conferences section — an attendance heatmap with each event's logo
- Timezone overlap card — compares the visitor's local hours with mine
- Dead Man's Switch — a farewell notice that appears if the site goes unbuilt for a set number of days, toggled from a root
config.yaml - Per-post
lastmodfor edited posts - Extra pages — friends, gear, links, projects, and video
- Responsive — mobile-first, with tables that switch to a card layout on small screens
- Dark-only theme tuned for reading at night
You need Bun and Node 24.x. npm and yarn are not supported.
bun install # install dependencies
bun dev # dev server on port 4321 (--host enabled)
bun run build # production build
bun lint:fix # auto-fix lint issuesUse bun run build, not bun build — the bare form runs Bun's own bundler instead of the build script.
Add a quoted password to a .md post in blog, infosec, ctf, musings, or research:
---
title: A locked post
date: 2026-10-07
password: 'replace: with a long unique passphrase'
---The build encrypts the rendered body and table of contents with AES-256-GCM and a PBKDF2-SHA-256 key (600,000 iterations). Readers enter the password to decrypt it in their browser. Passwords are not saved in browser storage. HTTPS or localhost is required. Remove password to publish the body normally; an empty password fails validation.
Titles, descriptions, cover images, dates, and URLs stay public. RSS omits the locked body. Images and downloads remain public files even when their links appear inside encrypted text. This supports static Markdown, not MDX components or scripts inside posts.
The source .md still contains the password and plaintext. Keep it in a private repository if the content needs to stay private, and deploy only dist/. Use a long, unique passphrase because visitors can download the ciphertext and try passwords offline. Rebuilding with a different password cannot revoke copies someone already downloaded.
The site is static, so the build output in dist/ can be hosted anywhere. Dockerfile.vercel builds it with Bun and serves the static files, and is used for the Vercel deploy.
CLAUDE.md— the single reference for conventions, architecture, content rules, and the checklist for adding a content collectionAGENTS.md— a thin pointer toCLAUDE.mdfor other agents and tools
Licensed under the MIT License.