Colophon
What this site is made of
Last updated September 9, 2026
What this site is made of, what it costs, and what we decided against. Versions come out of the lockfile rather than being typed here, so this page cannot claim a version the site is not running.
What it is built with
Ten packages are runtime dependencies, and three of those are the framework. Tailwind is in the table below because it produces every style on the site, but it is a build tool — none of it ships.
| Package | Version | What it does here |
|---|---|---|
| next | 16.3.4 | The whole site. Server components, so most pages ship no component JavaScript at all. Every route is server-rendered per request rather than cached, which is the price of a per-request Content-Security-Policy nonce. |
| react | 19.2.4 | With react-dom at the same version. The contact form is a server action, so it works before any JavaScript arrives and gets better after. |
| tailwindcss | 4.3.0 | Every style. The scale lives in one @theme block — see below. CSS is inlined into the HTML, so there is no stylesheet request blocking the first paint. |
| framer-motion | 12.38.0 | Scroll reveals below the fold, and nothing above it. Loaded as domAnimation rather than domMax, which is roughly half the size and the reason the nav underline is a native view transition instead. |
| lenis | 1.3.23 | Smooth scrolling. Switched off entirely when the visitor asks for reduced motion, which also hands scrolling back to the browser. |
| @base-ui/react | 1.4.1 | One component: the mobile menu. A dialog needs a focus trap, Escape, outside-click and the right ARIA — the hand-built version here got four of six right, which is the dangerous number. |
| zod | 4.4.3 | Contact form validation, on the server. The browser never sees it. |
| clsx + tailwind-merge | 2.1.1 + 3.5.0 | Composing class names so a later utility beats an earlier one instead of both landing in the DOM. |
| class-variance-authority | 0.7.1 | One component. It stays because it is honest about being a variant table; the buttons it used to be proposed for are plain strings in lib/button.ts instead. |
There is no analytics package, no tag manager, no font service, no cookie banner and nothing to consent to. That is not a privacy posture bolted on afterwards — the page makes no third-party requests at all, and a test fails the build if one appears.
What runs it
| Service | For | What happens if it is down |
|---|---|---|
| Vercel | Hosting and the server functions behind every page. | The site is down. There is no static fallback, because every response carries a fresh CSP nonce. |
| Resend | Delivering the contact form to a mailbox. | The form tells you it failed and gives you the email address directly, rather than pretending it sent. |
| Managed Redis, over HTTP | Counting contact form submissions per address so the form cannot be used as a mail cannon. | The limiter falls back to a per-instance counter, says so in the logs, and the message still goes through. Losing a real enquiry is worse than letting an extra one past. |
Neither the mail service nor the rate limiter has a client library here. Both speak plain HTTP, so both are a fetch call. Adding two packages to make nine lines of code look tidier would be an odd thing to do on a site that argues about payload.
What came out
This is the more useful half of a colophon. Everything below was built, worked, and was removed anyway.
| Removed | Why | What it cost |
|---|---|---|
| A WebGL hero object | 236 KB compressed, no device gate, and the headline could not paint until it rendered its first frame. Largest Contentful Paint measured 9.8 seconds on a throttled phone against a 2.5 second threshold. | The hero has no moving object in it. The code is kept, with notes, rather than deleted. |
| A loading screen | It held a 2,500 millisecond minimum over a page the server had already delivered, and gated the headline behind five separate conditions. | There is no branded entrance on a first visit. The page just arrives. |
| A rotating word in the headline | Every longer word became a new largest paint, and the measurement only stops when you touch the page. It also meant the sentence describing the work changed while you read it. | The headline says one thing. |
| tw-animate-css | Imported in the stylesheet, used nowhere. The one animate- class on the site is Tailwind's own. | 1,797 bytes of CSS, 247 of them compressed. |
| A component library button | It was dead code while the exact class list it would have replaced was hand-copied seventeen times. | Nothing. The classes moved to lib/button.ts, which is where they should have been. |
| Five described client projects | They were not real. | The work page now has this site on it and the numbers it can prove, which is a smaller claim and a checkable one. |
Decisions that went the other way
GSAP was asked for and not used. The site already carries two things that move elements; a third animation runtime on a page whose measured problem was JavaScript-gated rendering would have made the number worse. If something specific needs ScrollTrigger later, that is the moment to revisit it.
Base UI was added on purpose, and it is the one dependency added rather than removed during this rebuild. Hand-building a modal is a rule worth keeping: the version it replaced moved focus correctly on open, locked scroll correctly, set aria-expanded correctly — and let five Tab presses walk a keyboard user into the page behind the overlay, with Escape doing nothing.
Structured data for a FAQ was considered and declined. The process and services pages read like questions and answers but are not; marking up prose as a Q&A because it would be eligible for a rich result is the kind of thing that is technically allowed and quietly dishonest.
Every page is server-rendered on demand rather than cached at the edge. A per-request nonce and a static file are mutually exclusive, and a strict Content-Security-Policy is worth more here than a cache hit on a site this size — the server answers in about 40 milliseconds.
The three-dimensional object was parked, not deleted. It sits in docs/parked with its dependency line, the two API changes that have landed since, and the three conditions any future home has to meet: it cannot block the headline from painting, it cannot ship to a phone without a device gate, and it cannot need a caption explaining what it does.
Where the decisions live
If you are reading this to work out how the site is put together, these are the files that matter.
| File | Holds |
|---|---|
| app/globals.css | Colour, type scale, tracking, radius and easing as theme tokens; the hero keyframes; one focus rule for the whole site. |
| docs/tokens.md | Generated from the stylesheet above, including a count of the values that are still not on the scale. |
| lib/button.ts | Four button sizes, with a 44 pixel floor on the tap target. |
| lib/field.ts | Every form field, label and error. |
| lib/metadata.ts | Titles, descriptions and share images, per page. |
| lib/site-metrics.ts | Every number this site publishes about itself, each one next to the test that would fail if it stopped being true. |
How to check any of this
The claims on this site are guarded by tests rather than by good intentions, because the tests are the only part that keeps working after everyone has stopped paying attention.
| Suite | Proves |
|---|---|
| no-third-party.spec.ts | No request leaves this origin. Injecting one off-site script fails it by name. |
| a11y.spec.ts | axe-core against WCAG 2.0, 2.1 and 2.2 at A and AA, on every route, at three widths. |
| no-javascript.spec.ts | Every page renders, and the contact form submits, with JavaScript switched off. |
| largest-paint.spec.ts | The headline is the first thing eligible to be the largest paint on every page that has one. |
| zoom-and-contrast.spec.ts | No sideways scrolling at 400 percent zoom, and the primary button is still distinguishable in Windows High Contrast. |
| motion.spec.ts | Reduced motion is actually honoured, and the one animation between routes actually runs. |
| published-claims.spec.ts | Every number rendered on the work page is one this repository can back up. |
Each suite also runs against the deployed site rather than a local copy, by pointing one environment variable at it. That matters more than it sounds: before this rebuild, every claim about the live site rested on a separate script that nobody ran.
What this page does not tell you
It is about the site, not the desk. There is no editor, keyboard or monitor list here, because that is not what anyone is trying to find out when they land on a studio's colophon.
The numbers this site publishes, and the conditions each one was measured under, are on the work page. What has been tested for accessibility, what passed, and what has not been looked at yet are on the accessibility page.
Questions about this page? Email info@gbtn.studio.