Case Studies

How these were built

Three builds, start to finish: what each one actually needed, the decisions that shaped it, and the measured result.

All figures are real Lighthouse measurements (v12.8.2) taken against the live sites on 2026-07-20. Nothing is estimated.

01Client site — local business

TricepsGym

triceps-gym.pl
Next.js 15TypeScriptTailwind CSSshadcn/ui + Radix
TricepsGym — Client site — local business
Scroll to play

Brief

A gym in Wyszków needed a site that would actually be found by people searching for a gym nearby — and turn that visit into a walk-in. No CMS and no booking system: hours, pricing, trainers and the gallery change a few times a year, so every bit of complexity had to justify itself.

Decisions

01Static-first, no backend

Every page is rendered at build time and served as HTML from the edge. There is no database, no CMS and no server to keep alive. That is what buys the 0.9 s desktop LCP — and it means there is nothing to patch, nothing to breach and effectively nothing to pay for in hosting.

02Local SEO treated as the actual requirement

The point of the project was being findable for “siłownia Wyszków”. Consistent NAP (name, address, phone) on every page, semantic headings, real metadata and a clean sitemap — the unglamorous work that decides whether a local business shows up at all. It scores 100 on Lighthouse SEO.

03Accessible primitives instead of hand-rolled widgets

Menus, dialogs and the gallery carousel sit on Radix primitives via shadcn/ui, so keyboard navigation and screen-reader semantics are correct by construction rather than retrofitted after a complaint.

04Nothing moves while it loads

Every image and embed reserves its dimensions up front, so the page never reflows under the reader's thumb. Cumulative Layout Shift is a flat zero on both mobile and desktop.

Result

Performance (mobile)95
Performance (desktop)97
Largest Contentful Paint (desktop)0.9 s
Largest Contentful Paint (mobile)2.6 s
Cumulative Layout Shift0
Total Blocking Time (desktop)0 ms
SEO100
Best Practices100

Mobile

Performance95
Accessibility89
Best Practices100
SEO100

Desktop

Performance97
Accessibility89
Best Practices100
SEO100
02SaaS — live, paying subscribers

PodatekMT5

podatekmt5.pl
Next.js 15TypeScriptPostgreSQL (Neon)Drizzle ORMStripeBetter Auth
PodatekMT5 — SaaS — live, paying subscribers
Scroll to play

Brief

Polish traders settling MT5 profits for PIT-38 must convert every single position to złoty at the NBP mid-rate from the working day before the trade closed. Across a few hundred trades that is hours of spreadsheet work and very easy to get wrong. The product turns an exported MT5 report into PIT-38-ready figures in a couple of minutes.

Decisions

01Parse in the browser, not on the server

The MT5 report is parsed client-side — the trade history never leaves the device. For a file that is effectively somebody's brokerage statement, that is the difference between “trust me with it” and “there is nothing to trust me with”.

02Rates straight from the NBP API

Exchange rates come from the official NBP API and implement the art. 30b / art. 11a rule — the mid-rate published on the working day preceding the close, correctly stepping back over weekends and public holidays. The output has to hold up at a tax office, not merely look plausible.

03An embedded font, because Polish

jsPDF's built-in Helvetica cannot render ą ć ę ł ń ó ś ź ż, so the export used to silently drop diacritics from a document headed to Urząd Skarbowy. It now embeds a subsetted Lato, lazy-imported so the font is only downloaded when somebody actually exports a PDF.

04Getting a third-party player off the critical path

An embedded YouTube demo was loading its full player on every single visit — roughly 950 KB of third-party JavaScript and CSS, about half the weight of the page, on a page most visitors never watched the video on. It is now a lightweight thumbnail that only mounts the real player once you click it.

Result· before → after

Page payload1,833 KB807 KB
Performance (desktop)7491
Largest Contentful Paint (desktop)2.8 s1.4 s
Time to Interactive (mobile)12.8 s6.3 s
First Contentful Paint (desktop)1.3 s0.6 s
Total Blocking Time (mobile)410 ms320 ms
Performance (mobile)5863
Best Practices (desktop)96100

Mobile

Performance5863
Accessibility9595
Best Practices9396
SEO100100

Desktop

Performance7491
Accessibility100100
Best Practices96100
SEO100100

Mobile performance is still held back by roughly 210 KB of unused JavaScript in the application bundles. Splitting that out is the next planned pass — this page will be updated with the new measurement when it lands.

03WebGL experiment — self-directed

EIDOLON

eidolon-musuem.vercel.app
Three.js r160WebGLGLSLNative ES modules
EIDOLON — WebGL experiment — self-directed
Scroll to play

Brief

A deliberately useless website, and the only brief I set myself: find out how much atmosphere a browser can hold. EIDOLON is a museum whose entire collection doesn't exist — four artifacts (a maker's mark, a suspended chess endgame, a trefoil knot, an hourglass mid-pour), each rendered as 60,000 drifting grains of light that assemble as you descend through the halls.

Decisions

01The collection is computed, not downloaded

Every artifact is sampled at runtime into a Float32Array rather than shipped as a model file — the trefoil from its knot equation, the hourglass as a surface of revolution. That one decision is why a four-room museum of roughly 240,000 particles weighs 330 KB in total: there is no geometry to download, because there is no geometry until the page runs.

02One draw call per exhibit

All 60,000 grains of an artifact live in a single BufferGeometry drawn as THREE.Points, with drift and depth handled in a vertex shader and additive blending doing the glow. Sixty thousand scene objects would have buried the main thread; one buffer and a pair of shaders holds Total Blocking Time at 0 ms even on throttled mobile.

03No bundler, no framework

The whole site is native ES modules with an importmap pulling Three.js r160 straight from a CDN, plus 13 KB of application code. For a single-page piece with four scenes, a build toolchain would have been more machinery than the thing it was building — and it would not have made the result one byte smaller.

04The canvas is invisible to screen readers

The WebGL canvas is marked aria-hidden, and every exhibit carries a real heading, accession number and written description in the DOM. With the graphics off the museum still reads as a document — which is why a page that is visually nothing but particles still scores 100 on SEO and 94 on accessibility.

Result

Total page payload330 KB
Application code13 KB
Performance (desktop)99
Largest Contentful Paint (desktop)0.9 s
Total Blocking Time (mobile)0 ms
Cumulative Layout Shift0
Performance (mobile)71
SEO100

Mobile

Performance71
Accessibility94
Best Practices96
SEO100

Desktop

Performance99
Accessibility94
Best Practices96
SEO100

Mobile performance is 71, and the cost is first paint (4.5 s) while the particle field initialises on a throttled CPU — not page weight. That is the trade an experiment like this makes knowingly: nothing blocks (0 ms) and nothing shifts (CLS 0), but the first frame has real work to do.

Want results like these?

Tell me what you're building and I'll tell you honestly what it needs.

Start a project