Colophon - How this site was built
Not just displaying the answer, but showing the work. The tests run in continuous integration, and the instruction files that steered the generation are quoted here rather than described.
This page is the audit trail. If any of it does not hold up, you will know that before you have spoken to me.
The stack
Next.js 16 on the App Router, React 19, TypeScript 5.8, Tailwind 4.3. Deployed to Vercel. DNS at Cloudflare, grey-clouded — no proxy, no edge behaviour to explain later.
The design is Tailwind Plus Studio, a licensed template. I did not design a bespoke site and then imply otherwise. Buying the design meant the effort went into the parts a client actually pays for: the content model, the tests, the accessibility.
There are no tracking cookies and no third-party trackers (Cloudflare aside). Analytics is Vercel's own: cookieless, no fingerprinting, and served from this domain rather than someone else's. This site does not need a consent banner.
There is one third-party script, on the contact form. It is detailed below.
Content is stored two ways. Prose lives in MDX. Anything a component reads as named fields lives in a typed TypeScript module with a Zod schema over it. A missing field is a failing test rather than a blank section in production.
Vitest checks every content file against its schema. Playwright and axe-core check every route.
The rules that steered it
The generation was steered by instruction files committed to the repository — the same deliverable we leave behind on client work.
There are six instruction files: a project file describing the business and the template’s conventions, four rules files covering copy voice, component use, content architecture and testing, and one skill for scaffolding a case study.
This is the part of the voice file that did the most work, verbatim:
unlock · leverage · seamless · seamlessly · cutting-edge
state-of-the-art · revolutionise · empower · game-changer
transform your business · in today's fast-paced
journey · solutions provider · harness the power of
robust (as filler) · elevate · supercharge · best-in-class
at scale (as filler) · delve · tapestry · testament to
How much of it was AI-generated
Effectively all of the first draft. Every line after the initial template commit came out of a session with Claude Code.
That is not the same as unattended. I set the constraints, rejected work, and made the calls a model should not make on its own. Human reviewer re-wrote much of the AI generated content. The initial draft was used to structure and guide the final content.
Technical items that AI got wrong (AI generated confession below)
It broke the build with a comment. MDX only treats a block as code when it starts with import or export. A // comment placed above an export made the parser read the whole block as prose. The build failed pointing at a line that was not the problem.
It wrote a validator that passed its own tests and failed the compiler. The image schema accepted the right values at runtime, but the type it produced was not assignable to the one Next’s Image component expects. Caught by the type checker, not by the test suite that was written to catch exactly this kind of thing.
It put white text on a white background. The template ships one button style with an inverted variant for dark panels. The AI used the inverted variant for a secondary button on a light page. Nothing failed. It was simply invisible.
Its first attempt at proving the accessibility gate worked proved nothing. To check the gate could actually fail, it removed a label from a form field. The tests stayed green, which looked like a broken gate. It was not — a placeholder attribute was still supplying the name, so the change was not a real fault. The second attempt removed a button’s label and failed all ten routes, which is what a working gate looks like.
It shipped a layout bug that no test could see. Two paragraphs on the About page ran together with no space between them. Seventy-eight assertions passed. Someone had to open the page and look at it.
It loaded a script that never loaded. The contact form's spam check is a third-party widget. The framework's own component for loading scripts rendered nothing at all — no element on the page, no request on the network, no error in the console. The form looked finished and could never have been submitted. It took two rebuilds to find, because there was nothing to find: the failure had no symptom except absence.
The accessibility gate caught a bug that lasted four hundred milliseconds. Every section of this site fades in. Putting the contact form inside that animation meant its send button spent the fade below the minimum contrast ratio — perfectly legible by the time anyone looked at it, and a real failure while it was happening. The gate failed the page and was right to. Forms do not fade in here any more.
It typechecked on my machine and failed in CI. A generated type file, correctly excluded from the repository, was sitting on disk from an earlier build. Local runs were green because of a file the build server did not have. The fix was to make generating it part of the typecheck rather than something that had usually already happened.
It read a setting that had already been baked in. Some configuration in this framework is read when the site is built, not when it runs. Setting it and restarting changes nothing — it has to be rebuilt. That cost a cycle of concluding the spam widget was broken, when the page had simply been built without the key.
It wrote a document that could not be published. Asked to audit this site after it went live, it produced a good list: specific, ordered by cost, and right about the things it found. Adding that file to the repository was the obvious next step. It would also have published things about me that I have not chosen to publish, and passages from commercial plans that are nobody else's business. Nothing flagged that. The check that caught it was asking out loud whether the file was safe to publish before adding it — and the answer is why this repository is private. There is more on that at the end of this page.
The pattern is consistent. Type checking and tests catch a great deal. They catch nothing at all about whether a page looks right, reads right, or should be published in the first place. The worst of these had no symptom: no error, no failing test, nothing visibly missing. That still needs a person, and it is why "we used AI" is not by itself a claim about quality.
The one third-party script
The contact form is protected by Cloudflare Turnstile. It is the only script on this site loaded from a domain I do not control, and when it has to run an interactive challenge it sets a cookie for an hour — cf_chl_rc_n here, and cf_chl_persist on cloudflare.com, which is a third-party cookie.
The alternative was publishing an email address. An address on an indexed page is a scraping target, and the volume that attracts does not get filtered so much as it buries the one message a month worth reading.
I went with Turnstile because the form is the only thing on this site that accepts input from a stranger, which makes it the only thing here worth defending properly. Those cookies are classified strictly necessary, so this still does not need a consent banner.
Lighthouse
Median of three runs against korusoftware.com, measured after indexing was turned on:
| Mobile | Desktop | |
|---|---|---|
| Performance | 96 | 100 |
| Accessibility | 100 | 100 |
| Best practices | 100 | 100 |
| SEO | 100 | 100 |
On mobile, largest contentful paint is 2.6 seconds and total blocking time is 10 milliseconds. On desktop it is 0.8 seconds. Cumulative layout shift is effectively zero on both. Accessibility is 100 on every page, which is the same result the axe-core suite gates on before anything merges.
Mobile performance is 96, not 100, and an earlier version of this page said 100. That number was measured against a preview deployment, before the credibility strip was added to the home page and before analytics was switched on. Both cost something. Re-measuring against the real domain is what turned a true claim into a stale one, which is the argument for measuring the thing you actually ship.
SEO used to be missing from this table. The site was served with a Disallow: / robots file until it was on its own domain, so Lighthouse scored it 66 for being uncrawlable. That was the configuration working rather than a fault, and the gate has since been lifted.
None of it started where it is now. The first measurement was 93, with a largest contentful paint of 3.2 seconds, and the cause was the template's own entrance animation: every page's main heading was served at opacity: 0 and only became visible once the JavaScript had loaded and run. The heading is the largest element on the page, so the browser could not count it as painted until then. Above-the-fold content now renders opaque and slides into place instead, which also means every page is readable if the JavaScript never arrives.
Two hypotheses were wrong before that one was right. Render-blocking CSS, which Lighthouse itself suggested and which was never the problem — the stylesheet is seven kilobytes. And the font loading strategy, which was changed, measured three times before and after, and moved nothing at all.
The repository
The repository is not public. Ask, and I will walk you through it — screen share or a read-only invite.
That is a change of position. An earlier version of this page linked it, and the link is gone.
The instruction files are only half of what a build like this produces. The other half is the working record: a copy deck, session handover notes, and an audit of the live site that Claude wrote after launch. That audit is what caught the wrong turns taken, and it is also where the material I should not publish is. Draft claims that did not survive checking. Notes on my own history. Commercial plans that are nobody else’s business.
I could redact it. I would rather not publish a redacted audit trail and call it an audit trail.
So auditing what you wrote before you publish it is a step now, not a courtesy. It is the same category of work as the accessibility gate — a check that only earns its keep on the day it fails.
If you want the same approach pointed at a system that actually matters, that’s the whole offering: we’ll show you how to do this in your own development team, or on your own application.
Let’s have a chat.
Thirty minutes, no pitch deck. Tell me what you’re building or what’s stuck, and I’ll tell you what I’d do.
Ireland. Remote-first.



