I set one rule when building this site: a visitor's browser should never need my JavaScript in order to see the content. Every other decision follows from that.
No framework, just a build
There is no React, Vue or comparable runtime framework here — only plain JavaScript and CSS compiled by Vite. The production bundle ships no third-party runtime code to the visitor; every dependency either runs at build time or is a font package.
The measurable result is a small bundle. The real benefit is different: with a narrow dependency surface, the only place I have to look to understand what a page does is my own source.
Pages are written at build time
npm run build does two things. Vite produces the bundles, then my own prerender step writes every page to disk as real HTML:
npm run build
# vite build → dist/assets/*
# prerender → index.html, en.html, writing.html, writing/*.html,
# sitemap.xml, feed.xml, robots.txt, 404.htmlEvery address in dist/ returns readable text even if JavaScript never runs. The script that does run in the browser only enhances what is already on screen: the mobile menu, the reveal-on-scroll sections, and the language switch.
My favourite side effect of static generation is that there is less to break. No server-side API, no secrets, no database.
Motion is a choice
The wire form on the opening screen is drawn on a plain 2D canvas, not WebGL. It behaves in three ways:
- It stops drawing when it scrolls out of view
- It stops when the tab moves to the background
- It never starts when
prefers-reduced-motion: reduceis set
There is also a pause control in its corner. Someone who wants to stop an animation should not have to go into their system settings to do it.
Two languages, two real addresses
Turkish and English content live at separate addresses rather than behind a ?lang= parameter: / and /en.html. Each page carries its own canonical address and mutual hreflang links. The language switch works with JavaScript disabled, because there are real links underneath it.
This section
Writing goes through the same pipeline. Markdown files under content/writing/ are read during the build, converted to HTML and written out as static pages. The Markdown renderer lives inside the project; every character — code blocks included — is escaped before any markup is produced.
What lands here will mostly be notes on whatever I am working on: vulnerability analyses, problems met while building tools, and observations from lab work.