Work · 2026
This portfolio
Successor to a 33,000-line PHP application — static, bilingual, and free of a database.
At a glance
- No backend, no database, no login — served entirely as static files
- Content as Markdown behind a Zod schema that breaks the build, not the live site
- Bilingual DE/EN, with German unprefixed at the root
- Interactivity in vanilla TypeScript rather than a UI framework
Why rebuild
The previous version was a custom PHP MVC framework with eleven controllers, eight models and thirteen database tables. It handled user accounts with TOTP and passkeys, a ticketing system with an AI chatbot, four games with OpenAI opponents, and an EV charging calculator.
What it barely did was show projects. The portfolio was three of those pages, and the project data lived in a MySQL table — so it was not in git, not versioned, not reviewable. A portfolio whose content cannot be read anywhere has missed its own point.
The cut
Everything that did not serve showing work was removed: authentication, tickets, games,
calculators, the database, the server code. What remains is a static site sitting on the same
Apache host as before — just as a dist/ folder rather than a front controller.
Project content lives as Markdown in the repository. A Zod schema in content.config.ts
validates every field at build time. A missing year, a malformed URL, a project that exists in
only one language — each of those fails npm run build instead of surfacing on the live site.
Why no CMS
An admin area would have required a server, a session and password handling — precisely the attack surface whose removal was the point of the rebuild. A new project is instead one file per language and one commit. Rollback, diff and history come for free.
Frontend
Reactivity comes from a handful of vanilla TypeScript islands: filter and search on the project list, a command palette, the theme switch, a pointer-tracked spotlight on the cards. No UI framework, because the interactive surface is too small to justify one. Filtering degrades cleanly without JavaScript — you simply get the unfiltered list.
The palette continues the three themes the old site had: noir by default, light and sakura as
alternatives. Every animation sits behind prefers-reduced-motion.