Project case study · Full-stack development and site engineering
Personal Portfolio – Content-Driven Case-Study Platform
This site. A content-driven Next.js portfolio with its own scroll-choreographed animations, an interactive skill graph, and a CI pipeline that gates every change on real tests rather than on trust.
What it is
The site you are reading this on. Every page is built from typed JSON content files read through server-only modules, so the case studies, the skill graph, and the résumé all draw on the same facts rather than three copies of them.
It is also the one project here I use every day: every change goes through the same CI pipeline before it reaches this page.
Problem and approach
Problem
A portfolio is usually read once, and most of the engineering evidence on one lives in claims rather than in anything a reader can check.
Solution
Built the site itself as the evidence — a content-driven codebase with CI-gated tests, interactions that were measured rather than assumed, and a status document that records what is still wrong with it.
How it works
Content layer
Every project, role, and skill is a typed JSON file, read and validated at build time through server-only modules — the same data renders the case-study pages, the skill graph, and the downloadable résumé and CV.
Motion layer
GSAP and ScrollTrigger drive a pinned projects carousel with one single source of truth for scroll position; a Lenis-smoothed scroll and a canvas starfield sit underneath the rest of the site.
Verification layer
GitHub Actions runs lint, type checks, unit tests, and a Playwright suite against a production build on every change, before anything reaches this page.
How I built it
- 1.
Measuring before fixing
The projects grid was dropping to 83ms a frame on an iPad. Rather than guess at the cause, isolated each suspect in turn until backdrop-filter across every card turned out to be the entire cost, and fixed only that.
- 2.
Testing what a unit test cannot see
Added a Playwright suite that runs against a real production build, because the regressions this site actually had — a sideways-scrolling page, a document that prints blank mid-animation — are invisible to jsdom. It found a real bug on its first run: a full-screen dialog's Close button sitting under the fixed site header, so a click on Close was landing on the audio toggle instead.
- 3.
Writing down what is unfinished
Keep a status document recording known defects, deferred work, and the reasoning behind decisions like pinning a dependency — so the gaps are visible rather than something a visitor has to find themselves.
Challenges and decisions
A backdrop-blur effect across the projects grid dropped frame times to 83ms on an iPad, and the obvious guesses about the cause were wrong.
Approach: Measured instead of guessing, and isolated the cost by removing one suspect at a time until backdrop-filter across every card turned out to be the entire cost.
Lesson: Measuring the actual page beats reasoning about what should be slow.
A dependency update silently removed six icons the site's skill map depended on, and the build only caught one of them before stopping.
Approach: Checked every imported icon against the new version rather than trusting the first error, found five more missing, and pinned the dependency with the reasoning written next to the code that depends on it.
Lesson: A dependency bump is a claim, not a fact, until it has actually been checked.
Skills and technologies
What I learned
- How to tell a shipped feature from a shipped and verified one.
- That a status document written for the builder is more honest than one written for an audience.
- How much of 'it feels slow' turns out to be one measurable thing, once you stop guessing.
Outcomes
- 16.7ms
- iPad frame time, down from 83ms
- 266
- automated tests gated in CI