What it is
A boutique yoga studio in Stockbridge, Edinburgh, that does not exist. Fifteen public pages, five admin pages and a 404, including three class descriptions, five teacher profiles, a timetable, pricing, a visitor guide and seven long-form journal essays. About fourteen thousand words, all written for this project.
A studio is a good test because the category is full of sites that look expensive and do nothing. Soft photography, a nice typeface, and then a phone number at the bottom and no way to actually get into a class.
The one job
The whole site is arranged around getting somebody onto a mat, and I made that testable rather than aspirational. Three rules, each enforced by a test that runs against the built pages:
Every page-level primary button leads to the booking flow. Before that rule existed, three pages had an email enquiry as their loudest action, which is the exact failure the category is full of.
No page is a dead end. Every page, the 404 included, offers a route to booking from inside its own content and not just from the header. The 404 failed this and had to be rebuilt as a recovery page.
Intent carries. Choosing a class opens the flow at step two with that class selected. Choosing a time from the timetable opens it at step three, and all thirty-one slots carry their own link. Nobody is asked the same question twice.
The flow itself is four steps that ship as a single document, so with JavaScript switched off you get one readable form rather than a blank page. It ends in a confirmation and a calendar file. Nothing is submitted anywhere, and the page says so at the point where a real one would take your details.
The back office
Five admin pages - dashboard, bookings, schedule manager, members and a journal editor - reading the same data the public site reads. Change one file and the timetable updates on the homepage teaser, the schedule page, the admin week board and the teacher-load table, along with the counts written into the prose around them.
That shared model is most of what separates an expensive-looking site from a fragile one, and it is the argument for a generator rather than twenty-one hand-written pages.
The photography problem
Stock photography comes from a dozen photographers under a dozen lighting setups, and a page of it looks exactly like what it is.
So every source photograph runs through one shared grade in the build: saturation pulled down so loud props stop shouting, a per-channel warm push toward the studio's clay, a soft terracotta wash and a deterministic film grain. Framing is declared rather than automatic - each image can carry its own crop region, and the width budget is measured against the crop so a tighter frame never quietly costs sharpness. The overhead shot became hands pressing into a wood floor. The one frame with a mural in it was retired rather than carried.
Where a photograph would only be filler, the pipeline generates abstract light studies instead. The teachers are represented by generated tiles and initials, because they are invented people and a real face would be a claim about somebody who never agreed to it.
What it measures
First contentful paint of 224 ms on the homepage and cumulative layout shift of 0.000 on every page, in headless Chromium against the production headers. Ten requests for the homepage, no render-blocking CSS, and about 17 kB of JavaScript across the public site, roughly half of it the booking flow and loaded only there.
The stylesheet is inlined into every page rather than fetched, which is why first paint never waits on a round trip. Every image ships with intrinsic dimensions and a blurred placeholder, and every web font has a metric-matched local fallback, which is why the layout shift figure is a zero rather than a small number.
Verification was forty-six tests plus a per-page audit: no console errors, no failed requests, no dead internal links, one h1 per page, alt text everywhere, and no horizontal scroll at six widths from 320 px up.
Two things that would have shipped broken
The stylesheet was originally deferred using an inline onload handler. The site's own content security policy blocks inline script, so every page would have arrived in production completely unstyled. It was caught because the local preview server replays the production headers rather than serving files plainly. A content security policy you never test against is decoration.
Adding a package.json flipped the Vercel project from a static deploy to a framework build, which then failed looking for an output directory that does not exist. The fix was to tell the host explicitly that there is no build step, because the generator writes finished HTML into the repository root. Nothing to configure on deploy is the point of building it that way.



