/ Colofon

Hoe deze website is gebouwd: een technische tentoonstelling

Deze pagina is op zichzelf een portfoliostuk. Geen buildstap die je niet kunt lezen, geen afhankelijkheid die je niet kunt benoemen. Hier is precies wat hem aandrijft.

Het framework

De site draait op Pulsar, mijn eigen framework in PHP 8.5 en Rust. Dependency injection is gecompileerd, dus de container wordt bij de build opgelost en er is geen reflectie op het kritieke pad. De domein-, applicatie- en infrastructuurlagen blijven eerlijk gescheiden, en de kritieke modules dragen formele verificatie met Creusot.

Het tokensysteem

Alles wat visueel is, komt voort uit één set custom properties, de Precision Noir-tokens. Drie identiteitsaccenten, per sectie gewisseld met één attribuut. Geen enkele hardgecodeerde kleur leeft in een component.

#0039CBOntwikkeling
#D4A843Fotografie
#2D8F47Verkenning
1--accent, gewisseld
theme.css
/* one accent, re-pointed per section */[data-section="photography"] {  --accent: var(--color-photo);  --accent-glow: var(--color-photo-glow);}

Prestaties en toegankelijkheid

Twee zelf-gehoste variabele fonts, voorgeladen, met font-display swap. Responsieve afbeeldingen met AVIF en dan WebP, expliciete afmetingen zodat niets verschuift. JavaScript is minimaal, uitgesteld, en de kerninhoud werkt zonder. De doelen zijn echt: LCP onder 2,5 s, CLS onder 0,1, INP onder 200 ms.

Het contrast haalt WCAG 2.2 AA in beide thema's en op de gitzwarte galerijen. Elk interactief element heeft een zichtbare focusring, beweging valt weg onder reduced-motion, en een skip-link is het eerste wat een toetsenbord bereikt.

Hosting

AWS EC2 met Nginx en PHP-FPM 8.5. HTTP/3 en caching aan de rand via CloudFront, transactionele e-mail via SES, certificaten van ACM. Niets exotisch, alles bewust saai.

Aanbevolen beveiligingsheaders

Aan de rand geleverd. Hier is de set, als exhibit.

Formulieren verharden

De contact- en boekingsformulieren worden in lagen verdedigd: een verborgen honeypot-veld dat alleen bots invullen, een tijdval die inzendingen onder 2,5 s weigert, validatie en escaping aan de serverkant, snelheidsbeperking per IP, en een hCaptcha-uitdaging in reserve voor alles wat nog geautomatiseerd lijkt. De formulieren stellen ook duidelijk dat ze voor projectaanvragen en vragen zijn, niet voor koude verkoop.

headers.conf
Content-Security-Policy: default-src 'self';  img-src 'self' data:; font-src 'self';  script-src 'self'; style-src 'self'Strict-Transport-Security: max-age=63072000; includeSubDomains; preloadX-Content-Type-Options: nosniffReferrer-Policy: strict-origin-when-cross-originPermissions-Policy: geolocation=(), camera=(), microphone=()

Afbeeldingen en fonts

Elke afbeelding wordt geleverd als AVIF met een WebP- en JPEG-fallback, in een responsieve <picture> met expliciete afmetingen zodat niets verschuift tijdens het laden. De hero-afbeelding is gemarkeerd met fetchpriority="high"; alles onder de vouw is lazy. Fonts zijn zelf-gehoste woff2, voorgeladen en gewisseld, dus geen externe aanvraag op het kritieke pad.

picture.html
<span class="media" style="--ar: 4 / 5">  <picture>    <source type="image/avif" srcset="portrait-480.avif 480w, portrait-960.avif 960w" sizes="(max-width: 600px) 100vw, 480px">    <source type="image/webp" srcset="portrait-480.webp 480w, portrait-960.webp 960w" sizes="(max-width: 600px) 100vw, 480px">    <img src="portrait-960.jpg" width="960" height="1200" alt="Studioportret, zacht hoofdlicht" loading="lazy" decoding="async">  </picture></span>
fonts.css
/* preloaded in <head>, swapped, self-hosted woff2 */@font-face {  font-family: 'Montserrat';  src: url(/fonts/montserrat-var.woff2) format('woff2-variations');  font-weight: 100 900; font-display: swap;}