Static Astro blog source
  • CSS 34.8%
  • JavaScript 34.3%
  • Astro 17.8%
  • TypeScript 9.1%
  • Dockerfile 2%
  • Other 2%
Find a file
keo-sh c480778707 Document editorial theme behavior
Describe local Geist assets, system-aware persistence, and the expanded production checks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-22 18:18:42 -05:00
public Modernize blog theme 2026-07-22 18:17:54 -05:00
scripts Modernize blog theme 2026-07-22 18:17:54 -05:00
src Modernize blog theme 2026-07-22 18:17:54 -05:00
.dockerignore Set up static Astro blog 2026-07-22 00:24:38 -05:00
.gitignore Set up static Astro blog 2026-07-22 00:24:38 -05:00
astro.config.mjs Set up static Astro blog 2026-07-22 00:24:38 -05:00
Dockerfile Set up static Astro blog 2026-07-22 00:24:38 -05:00
justfile Set up static Astro blog 2026-07-22 00:24:38 -05:00
nginx.conf Set up static Astro blog 2026-07-22 00:24:38 -05:00
package-lock.json Modernize blog theme 2026-07-22 18:17:54 -05:00
package.json Modernize blog theme 2026-07-22 18:17:54 -05:00
README.md Document editorial theme behavior 2026-07-22 18:18:42 -05:00
tsconfig.json Set up static Astro blog 2026-07-22 00:24:38 -05:00

blog

Source for the static blog published at blog.keo.sh. Built with Astro, content collections, and a custom editorial interpretation of the terminal-modern theme. The site has locally bundled fonts, one small framework-free theme script, and no analytics or remote runtime dependencies.

Prerequisites

  • Node.js >=22.12.0 and npm >=9.6.5 (see engines in package.json)
  • just (optional; every recipe has an equivalent npm run script)
  • Docker with the buildx plugin (only needed for container image builds)

Commands

just npm Action
just install npm ci Install dependencies from the lockfile
just dev npm run dev Start the local dev server
just check npm run check Type-check with astro check
just build npm run build Build dist/ and run the safety/theme checks
just preview npm run preview Preview the production build locally
just clean npm run clean Remove generated dist/ and .astro/
just validate npm run validate Install, check, and build in one step
just image-build npm run image:build Build the container image locally
just image-push npm run image:push Build and push the container image

Run just with no arguments (or just --list) to see all recipes.

Adding a post

Create a new Markdown file under src/content/posts/, e.g. src/content/posts/my-new-post.md, with frontmatter matching the schema in src/content.config.ts:

---
title: 'My New Post'
description: 'One or two sentences describing the post.'
pubDate: 2024-06-01
# updatedDate: 2024-06-15
# draft: true
# tags:
#   - writing
#   - beginnings
---

Post body in Markdown.

The post appears automatically on the home page, in /rss.xml, and in the sitemap once draft is false (or omitted). The route is derived from the filename, so this example would be published at /posts/my-new-post/.

Tags are optional. When present, each trimmed tag string generates a matching archive at /tags/<slug>/ (for example, writing becomes /tags/writing/).

Theme

All colors, typography, spacing, radii, motion, and layout widths are defined as CSS custom properties in src/styles/theme.css. src/styles/global.css consumes those semantic tokens for both the warm light and graphite dark themes.

Geist Sans and Geist Mono are bundled as local Latin variable WOFF2 assets through the Fontsource packages. Their SIL OFL-1.1 license files are retained under public/licenses/; the browser never contacts a font CDN.

src/scripts/theme.js is the site's only client-side script. Astro emits it as a content-hashed same-origin asset and BaseLayout.astro loads it before the stylesheet so a saved preference is applied before first paint. With no saved blog-theme value, the site follows prefers-color-scheme; selecting Light or Dark stores that explicit choice. If JavaScript is disabled, CSS still follows the operating-system preference and hides the otherwise inert control.

scripts/check-theme-output.mjs verifies the color contrast contract, font output, bootstrap order, theme controls, and canonical favicon after every production build.

Public-content safety

This repository, and everything it builds, is expected to be safe for a public audience. Rendered pages, feeds, and source files must not contain real names, infrastructure details, credentials, internal URLs, host paths, or any information about the systems used to build or serve this site. npm run build runs scripts/check-public-output.mjs against dist/ after every build and fails if it finds source maps, credential-shaped strings, private IP addresses, or internal host/service terms. The separate theme-output check then validates the built visual contract. Keep post content and metadata equally generic.

Container image

The Dockerfile builds the site with Node and serves the static output with nginx-unprivileged. Images are tagged with the full Git commit SHA only; there is no latest tag.

# one-time, interactive: log in to the registry
docker login git.keo.sh

# build and push git.keo.sh/keo/blog:<full-git-sha>
just image-push

scripts/image.mjs derives the tag from git rev-parse HEAD, refuses to run against a dirty worktree, and never accepts or prints credentials - authentication is handled entirely by the docker login step above.

CI

There is no CI workflow in this repository yet. Publishing is a manual, local just image-push today. Once a hardened, self-hosted runner is available for the Forgejo instance at git.keo.sh, this project should migrate to a Forgejo Actions workflow that runs just validate and just image-push on tagged commits.