So entsteht diese Seite: eine technische Ausstellung
Diese Seite ist selbst ein Portfoliostück. Kein Build-Schritt, den du nicht lesen kannst, keine Abhängigkeit, die du nicht benennen kannst. Hier ist genau das, was sie antreibt.
Das Framework
Die Website läuft auf Pulsar, meinem eigenen Framework in PHP 8.5 und Rust. Dependency Injection ist kompiliert, der Container löst also beim Build auf und es gibt keine Reflexion auf dem heißen Pfad. Die Domänen-, Anwendungs- und Infrastrukturschichten bleiben ehrlich getrennt, und die kritischen Module tragen formale Verifikation mit Creusot.
Das Token-System
Alles Visuelle leitet sich aus einem Satz von Custom Properties ab, den Precision-Noir-Tokens. Drei Identitätsakzente, pro Abschnitt mit einem einzigen Attribut getauscht. Keine fest codierte Farbe lebt in einer Komponente.
/* one accent, re-pointed per section */[data-section="photography"] { --accent: var(--color-photo); --accent-glow: var(--color-photo-glow);}
Performance und Barrierefreiheit
Zwei selbst gehostete variable Schriften, vorgeladen, mit font-display swap. Responsive Bilder mit AVIF, dann WebP, explizite Maße, damit nichts verrutscht. JavaScript ist minimal, verzögert, und die Kerninhalte funktionieren auch ohne. Die Ziele sind real: LCP unter 2,5 s, CLS unter 0,1, INP unter 200 ms.
Der Kontrast erfüllt WCAG 2.2 AA in beiden Themes und auf den tiefschwarzen Galerien. Jedes interaktive Element hat einen sichtbaren Fokusring, Bewegung verschwindet unter reduced-motion, und ein Skip-Link ist das Erste, was eine Tastatur erreicht.
Hosting
AWS EC2 mit Nginx und PHP-FPM 8.5. HTTP/3 und Caching am Rand über CloudFront, transaktionale Mail über SES, Zertifikate von ACM. Nichts Exotisches, alles bewusst langweilig.
Empfohlene Sicherheits-Header
Am Rand ausgeliefert. Hier ist der Satz, als Exponat.
Formularhärtung
Die Kontakt- und Buchungsformulare werden in Schichten verteidigt: ein verstecktes Honeypot-Feld, das nur Bots ausfüllen, eine Zeitfalle, die Einsendungen unter 2,5 s ablehnt, serverseitige Validierung und Escaping, Rate-Limiting pro IP und eine hCaptcha-Challenge in Reserve für alles, was noch automatisiert aussieht. Die Formulare sagen außerdem klar, dass sie für Projektanfragen und Fragen sind, nicht für Kaltakquise.
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=()
Bilder und Schriften
Jedes Bild wird als AVIF mit WebP- und JPEG-Fallback ausgeliefert, in einem responsiven <picture> mit expliziten Maßen, damit beim Laden nichts verrutscht. Das Hero-Bild ist mit fetchpriority="high" markiert; alles unter dem Falz ist lazy. Schriften sind selbst gehostetes woff2, vorgeladen und getauscht, also keine Drittanbieter-Anfrage auf dem kritischen Pfad.
<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="Studioporträt, weiches Führungslicht" loading="lazy" decoding="async"> </picture></span>
/* 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;}