A credibility page. Someone searches the name, lands here, and quickly knows who Kadir Günay is, what he can be hired for, where he has worked, and how to reach him. Not a portfolio — the CV lives at cv.kdrgny.com.
npm install
npm run dev # http://localhost:5180
npm run build # → dist/
npm run preview # serve dist/ on :4173dist/ is plain static files — no server, no runtime, no third-party requests.
Drop it on Vercel, Netlify, Cloudflare Pages or GitHub Pages as-is.
Four files carry the whole site.
| Path | |
|---|---|
index.html |
every word on the page, plus meta and JSON-LD |
src/styles/main.css |
type, layout, surface, light and dark palettes |
src/styles/fonts.css |
the four @font-face declarations |
src/main.js |
one IntersectionObserver, plus the name reveal |
- The page works with no JavaScript. The reveal is additive: JS puts
.riseon<html>, and only then does anything start hidden. If the module never loads, the page is simply already visible. .name__chspans are created at runtime.index.htmlholds the name as one string; JS splits it, freezes each glyph's width, and varies weight by pointer distance. Freezing the widths is not optional — without it every weight change reflows the line.--lx,--lyand--syare unitless numbers set on<html>by JS and multiplied into percentages inside the.lightgradients. Pointer, pointer, scroll progress.- Never hard-code the years.
src/lib/since.jsholdsSINCE = 2007and spells the count out.vite.config.jssubstitutes__YEARS__in the HTML at build and at dev-server start, so crawlers and no-JS visitors get the current figure;main.jsrewrites every[data-years]on load, which covers a year in which the site is never rebuilt. Adddata-yearsto any new mention. .nameline-height is 1.26 for a reason. Fraunces measures 1.235em from ascent to descent, and the reveal clips to the line box — anything tighter guillotines the descender of the y in Günay. Measure withmeasureText().fontBoundingBoxAscent/Descentbefore changing it; do not eyeball it.- Fonts are declared by hand. Fontsource's per-subset entry points ship no
unicode-range, so importing latin and latin-ext together leaves two faces claiming every codepoint — and the one holding no basic Latin wins. The type falls back to a system serif with no error and no warning. - Fraunces keeps its optical-size axis (
opsz144 for the name, 12 for the numerals, 28–40 in between). That axis is why the face is here; it costs 31 kB over the weight-only file. - Latin-ext is not optional.
Doğuşneeds it. Company names are set in Fraunces partly for that reason: Fraunces' latin-ext file is 26 kB lighter than Inter's. - Dark is the base, light is opt-in.
:rootcarries the dark palette with no attribute and no media query, so there is nothing to resolve before the first paint.:root[data-theme='light']overrides it. The OS preference is deliberately ignored; the inline script in<head>readslocalStorage['kdrgny:theme']before CSS so a returning light-theme visitor never sees the dark default flash. Switching back to dark removes both the attribute and the stored value — the default leaves no residue. - Both palettes are authored, not one inverted from the other. Changing a colour means changing it in two places on purpose.
All of it is in index.html, in reading order. When the role changes, update
the eyebrow, the first .role, the JSON-LD jobTitle and worksFor, and the
<meta name="description">.
Both are committed. qlmanage -t renders SVG including @font-face with
base64 woff2, but always fits it into a square, so the card is authored
1200×1200 with the artwork in a centred 630-tall band and cropped back out with
sips -c 630 1200.