Astro · CSS · portfolio · 6 September 2026

Building this site

Why I chose a static Astro site, native browser transitions, modern CSS, and a small file-based CMS.

I wanted a portfolio that I could keep current without turning it into another application to maintain. The pages are static. The content is in git. The interesting parts come from the browser.

Why Astro

Most pages on this site are documents. A project page needs a title, a case study, and a README. An article needs readable HTML. Neither needs a React runtime.

Astro gives me content collections with schemas and renders the result to HTML. JavaScript is limited to the theme switch, filters, and fallbacks for browser features. That keeps the first response useful on a slow phone and makes a broken script less dramatic.

Content without a server

Pages CMS is the editor. It writes YAML, Markdown, and media files into this repository. GitHub Actions builds those files and deploys them to GitHub Pages.

There is no content database to export later. Removing the CMS would remove the editor, not the content.

Public repositories tagged portfolio are read during the build. Their names, links, languages, topics, and READMEs stay current without a component edit. Case-study copy remains curated because a repository description is not a hiring story.

View Transitions, with an exit

The site opts into native cross-document View Transitions. A project title keeps its identity as navigation moves from the list to the detail page. Browsers that support it draw the morph. Other browsers perform a normal navigation.

The theme switch uses document.startViewTransition for a short color wipe. The code checks that the method exists first. Reduced-motion uses the same instant path as an unsupported browser.

That fallback is important. Navigation cannot depend on an animation API.

CSS I could use for real

The palette is written in hex first, then upgraded to oklch() inside @supports. Titles use text-wrap: balance. Body introductions use text-wrap: pretty. Ignoring either property still produces readable text.

The project list uses a view timeline for a small entry shift. Without animation-timeline, it is simply a list. Image overlays use the Popover API where available, with a dialog path behind it.

FeatureSupported browserFallback
Cross-document View TransitionsShared project and article motionNormal page navigation
oklch() and light-dark()Perceptual color tokensHex tokens and a theme class
animation-timeline: view()Short list-entry motionStatic rows
Popover and @starting-styleAnimated top-layer overlayDialog with instant open
text-wrap: balance and prettyCleaner headings and paragraphsNormal wrapping

Build output

The same résumé YAML renders the about page and an A4 PDF. A build-only Playwright pass adds the private phone number to the PDF, captures Open Graph cards, then removes the print HTML from the deployed files.

GitHub’s API supplies the activity figures. If it is unavailable, those figures disappear. The site does not substitute made-up counts.

This is a first release. The useful test is whether someone can tell what I do, inspect the work, and download the résumé without learning how the site works.