Every claim below can be checked against the repository, which is the only reason this page exists. A site that says it cares about detail should be willing to show its own.
The stack
Next.js 16 on the App Router, React 19, TypeScript 6. Every page is prerendered to static HTML at build time; there is no server rendering a request, no database and no API.
TypeScript 7 was the newer choice and was reverted, because typescript-eslint does not
support it yet, so taking it would have meant giving up linting to gain nothing this
project needs. Newest and best are different questions.
There is no CSS framework. The design is about 1,400 lines of plain CSS driven by custom properties, and six runtime dependencies: React, React DOM, Next, and three packages for MDX. Nothing was installed to make an animation work.
Three typefaces, one job each
Anybody sets the name, and nothing else on the site. It carries a width axis running
from 50 to 150, and at wdth 150 with wght 200 it is a hairline extended grotesque:
wide, flat-sided, closer to a drawing than to lettering. Set any heavier it turns into
motorsport graphics, so the light end of that axis is not a preference, it is the entire
reason the typeface is here.
The name happens to be five letters over five letters. Stacked at that width it becomes a
solid block of type, which is why the site has no logo: at that size the name is the mark.
Its size is not a step on a type scale either. The string "ADNAN" measures 5.168 times its
own font size, so the CSS is clamp(3.25rem, 16.6vw, 14.2rem), a number that was
measured in a browser rather than guessed, and which fills the column at every width.
Instrument Sans carries everything written as language. A low-contrast neo-grotesque with open apertures and terminals cut on a slight angle, which is what keeps it from reading as Helvetica. It is quiet at display size and comfortable at 17px, which is the correct division of labour when something else is doing the shouting.
Azeret Mono carries everything that is data: dates, statuses, counts, index numbers, labels, domains, the handle. That split is the system. If a string is a fact you could look up, it is monospaced; if it is language, it is not. A reader never has to be told the rule to start using it.
The colours
Not #000, and not grey. The page sits on #070A12, a blue-black with real chroma in
it, cool the whole way down.
Two things make a dark interface read as deep rather than flat, and neither is the background colour. The first is that the backgrounds occupy a very narrow band of luminance while the text ramp is wide: three text colours spanning 16.7:1 down to 5.4:1, over surfaces that barely differ. The second is that surfaces are translucent white laid over the base rather than opaque colours of their own, so every panel inherits the hue underneath it and layering composes correctly. Borders are lit edges for the same reason: on a dark ground, a dark shadow is invisible.
The accent is a vermilion, #F4623A, at 6.3:1. It is warm against a cold field, which is
where the tension in the page comes from, and it is a signal colour in the instrumentation
sense rather than a brand colour. The studio's colour is a bright blue; the point of a
personal site is that it is not the company.
Every contrast number above was computed, not estimated. The faintest text on the site is 5.1:1.
The build refuses to lie
The interesting part is not the stack, it is a script that runs before next build:
node scripts/check-content.mjs && next build
It fails the build when a published project claims a state it cannot support: a project
marked live with no URL, a duplicate slug, an article dated in the future, a summary too
long to survive as a meta description. It warns, without failing, when the /now page has
gone ninety days without an update.
This exists because a personal site rots in a specific and predictable way. Links die, a project quietly stops running, a page that says "currently" stops being current. None of that shows up in a type check or a passing test, and none of it is noticed by the person who wrote it, because they already know what it was supposed to say.
Writing the rule into a script converts a thing you have to remember into a thing that fails loudly. That is worth more than any amount of good intentions about maintenance.
What is deliberately absent
No analytics, no tag manager, no cookie banner, because there are no cookies. No
llms.txt: Google's own documentation states that Search ignores such files, and no
other major engine has announced that it reads one, so adding it would be decoration
rather than discoverability.
No cards. Nothing on this site sits inside a rounded rectangle, and there are no pill buttons anywhere. A row is a hairline and some space; a surface has to earn itself by meaning something. The only repeated interactive element is a tracked label over a rule that fills from the left.
There is no dark-mode flash, because the theme is resolved by a small inline script before the first paint rather than by an effect after it.
The measurements
Measured on the production build, not the dev server, and not rounded in my favour:
- Homepage HTML: 37 KB raw, 7 KB compressed.
- Fonts on the critical path: three files, 110 KB. The previous design used two files and 172 KB, so gaining a third family made the site lighter, not heavier.
- JavaScript: about 169 KB compressed, and essentially all of it is React and the Next runtime. The application's own client-side code is one component, the theme toggle.
- Third-party requests: none. No analytics, no font CDN, no embeds.
That JavaScript number is the honest weak point. A site this static does not need a client-side framework at all, and roughly all of the bytes a visitor downloads are the cost of using one. It buys the routing, the image handling and the metadata system, which is a real trade and not obviously the wrong one. But it is a trade, and a page of text should probably not cost that.
Which is the useful thing about measuring: the constraint you were not planning to justify is the one the numbers find.