goonk.se
Highlights
- Migrated 100+ WordPress posts to Markdown via a custom XML → Turndown conversion pipeline
- Astro static site with Markdown-driven project pages and content collections
- Self-hostable via Docker + nginx (plus optional Immich photo proxy)
- Hidden easter eggs and achievement system for curious visitors
A complete reboot of my personal site after years of WordPress — including a full migration of 100+ blog posts from the WordPress XML export to Astro Markdown.
What it is
- A small collection of pages: CV, highlights, projects, blog, photos, homelab
- 100+ blog posts migrated from WordPress, going back to ~2010
- Markdown-driven project write-ups in
src/content/projects/ - Static site generation with Astro 5.0
- Dockerized deployment to my own infrastructure
- Hidden easter eggs and achievements for curious visitors
Why I rebuilt it
Years of WordPress felt heavy. I wanted something where adding a project or a blog post was just dropping a Markdown file in a folder. No database, no plugin updates, no security patches at 2am.
Also wanted full control over hosting and styling without fighting themes.
The migration
The old blog ran on WordPress with around 100 posts from 2010 onward. WordPress’s export format is a standard RSS XML file with all post content stored as HTML, so migration meant:
- Parsing the XML export with
xml2js - Converting post HTML to Markdown with
turndown, with custom rules for WordPress-specific markup (caption shortcodes, figure elements, embedded media) - Rewriting internal links from
/year/month/slugto the flat/blog/slugstructure - Downloading all media from the WordPress media library and rewriting image paths
- Mapping WordPress post metadata (title, date, categories, tags, author) to Astro frontmatter
Each post landed as a .md file in src/content/blog/. The content collection schema validates every post at build time — if a frontmatter field is missing or malformed, the build fails loudly.
Tech stack
- Astro 5.0: Static site generator, fast builds, no client-side JS bloat
- TypeScript: Type safety for content collections and frontmatter
- Docker + nginx: Self-hosted deployment with reverse proxy
- Gitea CI/CD: Automated builds on push to main branch
- Immich integration: Optional photo proxy for the photos page
Features
- Content collections: Type-safe markdown with Zod schemas
- Project pages: Automatic routing from
src/content/projects/*.md - Debug mode: Hidden developer comments throughout (click the “Updated” date)
- Easter eggs: 19 hidden achievements for explorers (Konami code, typed commands, URL params, secret pages)
- Achievement system: LocalStorage-persisted tracking with milestone rewards
- Photo integration: Pull images from self-hosted Immich instance
- Responsive design: Works on mobile, doesn’t look like 1998 (unless you trigger ?1998=true)
Fun stuff
The site has a complete achievement system with easter eggs scattered throughout:
- Hidden pages at
/secretand/hiring - Konami code (↑↑↓↓←→←→BA)
- Typed commands (try typing “gravity”)
- URL parameters (?matrix=true, ?coffee=true, ?dark=true)
- Time-based surprises (visit between midnight and 4am)
- Interactive elements (click the profile photo 10 times)
- Paint mode, vim mode, emacs mode
- And more…
Check out the /achievements page to see what you’ve unlocked.
Deployment
Runs in Docker on my homelab, built automatically via Gitea CI whenever I push to main:
docker compose up -d
The container serves static files via nginx, with optional reverse proxy configuration for the Immich photo backend.
Link
Live at cv.dev.explewd.com/ (for now)