Work Case study
This Portfolio
A static site written by hand, with no framework and no build step to speak of.
- HTML
- CSS
- JavaScript (ES modules)
- Node.js
- Netlify
01
The Idea
A portfolio is a product with exactly one user story, so it seemed worth building it the way I would build anything else: a design system rather than a stylesheet, content as data rather than as markup, and no dependency I could not justify.
There is no framework here. The pages are generated from the content files by a small Node script that also serves them in development, so the markup a browser receives is the markup in the repository.
02
Design Evolution
Three published versions, all still in the git history.
V1 · 4 August 2026 The first published version. The editorial identity is already here: the rail, the wordmark, the serif italic accent.
V2 · 17 August 2026 Contrast fixed against the paper background, a metadata line added above the wordmark, and the first real calls to action.
V3 · 18 August 2026 The rebuild. Content moved into data files, every project got a case-study page, and the hero gained the two facts a recruiter looks for first. The small letter-spaced labels above headings came out.
03
Technical Decisions
The deploy is an allowlist
The build copies a named list of files into dist/ rather than ignoring a list of secrets. Publishing the repository root would put .env and .git on a public CDN; with an allowlist a new asset directory has to be added deliberately.
Why it matters: The failure mode of the safer-looking option is a leaked SMTP credential at /.env.
Content is data, markup is generated
Projects, skills and dates live in content/*.js. The homepage, every case-study page, the timeline and the command palette all read from those files, so a date exists in exactly one place.
Videos are loaded only if you reach them
Each preview is preload="none" and starts on its first intersection, then pauses when it leaves. A visitor who never scrolls to a case study never downloads its video.
The previews were re-encoded, not just compressed
The two demo videos were 42 MB and 52 MB and tracked in Git LFS, which meant a plain clone produced 133-byte pointer files and both previews failed silently. Re-encoded to 1.1 MB and 2.6 MB, audio dropped, and committed directly.