Case study · Project
A static Astro site in two languages, where every figure is computed from a typed record and every page is tested in a real browser.
- Stack
- Repository
- github.com/bgalvandev/brunogalvan.dev
- Demo
- No public demo
Section 1 of 4: Context
My portfolio, rebuilt in September 2026 in the grammar of one design master. It ships as static HTML with one bundled script for motion; every visible string lives in a Spanish and an English catalog, and every number is derived from the record when the site builds.
Section 2 of 4: Architecture
Record
src/data holds the roles, projects and stack; years, counts and the career chart are computed from it at build time.
Routes
Localized URLs live as pairs in one file, so every page exists in both languages with reciprocal hreflang.
Tokens
Colours and faces come from semantic tokens with a light and a dark value; a dark band re-resolves them by switching the colour scheme.
Motion
GSAP and Lenis run in one context that reverts completely under reduced motion or the pause control.
Section 3 of 4: Decisions
No framework in the browser
Static output; the only script is the motion bundle, and every page works without JavaScript.
A policy built from hashes
The build hashes every inline script and style into the Content Security Policy, so an edited block can never ship with a stale hash.
Tested as shipped
Playwright runs the production build with axe in both themes and replays the policy on every page.
Decisions on record
Hosting, the motion library and smooth scrolling each have an ADR with the options compared and the cost in kilobytes.
Section 4 of 4: In the code
// Cloudflare Pages reads `_headers` from the build output. The Content Security
// Policy is derived from the built HTML: every inline script and style block
// becomes a sha256 source, so the pre-paint theme initializer and the font
// pipeline's @font-face block keep working without 'unsafe-inline', and an
// edited inline block can never ship with a stale hash.
const inlineScript = /<script(?![^>]*\bsrc=)[^>]*>([\s\S]*?)<\/script>/g;
const inlineStyle = /<style[^>]*>([\s\S]*?)<\/style>/g;
const sha256 = (text) =>
`'sha256-${createHash('sha256').update(text).digest('base64')}'`;