My personal website built with Astro, Tailwind CSS v4, and TypeScript. Has a yarn ball cursor with Verlet rope physics and a fun little game mode where monsters attack the blog entries. You can try to defend against the monsters with the yarn ball, but it's quite hard!
npm install
npm run dev # localhost:4321
npm run build
npm run previewFour content collections defined in src/content.config.ts.
Two sources, combined by src/lib/blog-loader.ts:
Drop a .md file in src/content/blog/. The filename becomes the slug.
Frontmatter:
---
title: "My Post Title"
description: "A short description"
pubDate: 2025-01-15
updatedDate: 2025-02-01 # optional
tags: ["astro", "web"] # optional, defaults to []
heroImage: "/images/hero.png" # optional
draft: false # optional, defaults to false (drafts are excluded)
---
Your markdown content here.Add a repo to the githubRepos array in src/content.config.ts. The README is fetched at build time and rendered as a blog post.
const blog = defineCollection({
loader: blogLoader({
contentDir: "./src/content/blog",
githubRepos: [
{
repo: "LiquidFun/godot-tween-cheatsheet", // required: owner/repo
tags: ["godot", "gamedev"], // optional
title: "Custom Title", // optional, defaults to repo name
description: "Custom description", // optional, defaults to repo description
pubDate: "2024-06-01", // optional, defaults to repo creation date
},
],
}),
// ...
});Both collections are parsed from the LiquidFun GitHub profile README by src/lib/github-loader.ts. To add a project or game, add an entry to the relevant section in the LiquidFun/LiquidFun profile README:
<a href="https://github.com/User/repo" title="Project Name - Description of the project">
<img src="https://github.com/User/repo/blob/main/thumbnail.png" ...>
</a>Pages are at /projects/<slug> and /games/<slug>.
Loaded from LiquidFun/CTF-Writeups by src/lib/ctf-loader.ts, which clones/pulls the repo into ctf-writeups/ at build time.
Directory structure:
ctf-writeups/
EventName2024/
ChallengeName/
README.md # writeup content (title extracted from first # heading)
media/ # optional, images/files referenced in the writeup
AnotherCTF2023/
SomeChallenge/
README.md
To add a new writeup:
- Create a directory under the event name (e.g.,
EventName2024/ChallengeName/) - Write a
README.mdwith a# Titleheading and your writeup content - Place any images in a
media/subdirectory and reference them as./media/image.png
The loader automatically:
- Parses the year from the event directory name
- Copies media files to
public/ctf-assets/<event>/<challenge>/ - Rewrites media paths in the markdown accordingly
- Generates tags from the event name
Pages are rendered at /ctf/<event>/<challenge>.
The about page is a standalone Astro component at src/pages/about.astro. Edit it directly — it's plain HTML/Astro markup, not a content collection.
Pushing to main triggers the GitHub Actions workflow (.github/workflows/deploy.yml), which:
- Checks out the repo
- Restores cached CTF writeups
- Installs dependencies and builds the site
- Deploys
dist/via rsync to the Hetzner server
Required GitHub secrets: HETZNER_HOST, HETZNER_USER, DEPLOY_SSH_KEY, DEPLOY_PATH.
