How this site is built
Built to the standard we sell.
This site runs behind the same budgets, gates and accessibility checks we put on client work. Every figure here says what it is: Measured, with its source and date, or Target, a limit the build holds us to.
- Framework
- Astro, prerendered
- Host
- Cloudflare Workers
- Design system
- FIRN
- Cookies
- None of our own
- Measured
- 2026-10-13
Measured, not claimed
The numbers this site can prove about itself. The JavaScript figure is read from the build and checked by it, so it can’t drift from the truth.
- 100×4Lighthouse, mobile: all four categoriesMeasured
- 100×4Lighthouse, desktop: all four categoriesMeasured
- 1.6KBJavaScript at load, this pageMeasured
- 0Cumulative Layout Shift, home pageMeasured
- 0msTotal Blocking Time, home pageMeasured
Lighthouse: Lighthouse 13, median of 3, on a local production build of the home page, ; layout shift and blocking time from the same runs. JavaScript: gzip size of the one script this page runs at load.
Limits the build enforces
A check after every build fails it if any page goes over, and axe holds every template to WCAG 2.2 AA in both themes. These are limits, not readings, so they carry the Target label.
- 100Lighthouse in every category, every page, mobile and desktopTarget
- ≤ 2,048BThe one script a page runs at loadTarget
- ≤ 6KBEach island, fetched only on its triggerTarget
- ≤ 40KBAll JavaScript, site-wideTarget
- ≤ 16KBInline CSS per page, no stylesheet requestsTarget
- ≤ 60KBAll font files, self-hosted woff2Target
- AA2.2WCAG, light and dark themesTarget
- 44pxMinimum touch targetTarget
Gzip sizes. One documented exception: the home page may inline a little more CSS for its CSS-only chapters. No page loads a stylesheet or a font from another domain. The theme’s text and surface color pairs are checked for contrast in the unit tests.
Open Machine view to check these numbers on any page. It outlines the page’s sections, reads load timings, bytes and requests from your own browser, and sets them beside these budgets. Every page’s footer has it as “Measure this page”. It needs JavaScript, and nothing it reads is sent anywhere.
- Reduced motion
- Forced colors
- Increased contrast
- Keyboard only
- Screen readers
- JavaScript off
Five checks. Try to break the build.
Every pull request to this site runs the same five steps, in order, and a failure stops the line. Pick a change below to see which check stops it, and what it printed.
Types
Strict TypeScript across pages, components and server code.
Unit tests
Lead pipeline, structured data, the contrast of the theme’s color pairs, content rules.
Build
Pages prerendered to static HTML with their CSS inlined, and the Worker bundled.
Verify
Content in the HTML, one H1, valid structured data, no prices, every budget held.
Accessibility and journeys
axe against WCAG 2.2 AA in light, dark and mobile, plus the real journeys in a browser.
A clean change: All 5 passed. Merged. Passed: Types, Unit tests, Build, Verify, Accessibility and journeys.
$ pnpm check · test · build · verify · test:e2e Result (196 files): - 0 errors Tests 274 passed | 16 skipped (290) [build] Complete! ✓ 29 pages checked · JS at load: entry 1502 B gz 196 skipped 463 passed (8.2m)The rule All five steps pass, in order, so the change can merge.
Now try to break it.
Rename a prop, miss one page: Stopped at Types. Not merged. Stopped it: Types. Not run: Unit tests, Build, Verify, Accessibility and journeys.
$ pnpm check src/pages/approach/how-this-site-is-built.astro:94:26 - error ts(2322): Property 'label' does not exist on type 'IntrinsicAttributes & Props'. Result (196 files): - 1 errorThe rule astro check runs strict TypeScript over every page, component and server file. One error fails the step.
The page that still used the old name would have shipped broken.
Lighten the grey text a touch: Stopped at Unit tests. Not merged. Passed: Types. Stopped it: Unit tests. Not run: Build, Verify, Accessibility and journeys.
$ pnpm test ❯ tests/unit/tokens.test.ts (92 tests | 4 failed) × moraine on snow-25 >= 4.5:1 × moraine on snow-0 >= 4.5:1 × moraine on rime >= 4.5:1 × moraine on firn-deep >= 4.5:1 AssertionError: expected 3.9654034385519052 to be greater than or equal to 4.5The rule The unit tests hold every text and surface pair in the palette to WCAG: 4.5:1 for body text, 3:1 for large text and controls.
#56606B to #737D88 looks subtle on a good monitor and is hard to read on a phone in daylight.
Type “<2 seconds” in a page: Stopped at Build. Not merged. Passed: Types, Unit tests. Stopped it: Build. Not run: Verify, Accessibility and journeys.
$ pnpm build [ERROR] [vite] ✗ Build failed in 1.96s 55:22: Unexpected character after `<`, expected a valid JSX tag (note: to create a link in MDX, use `[text](url)`) (mdx-jsx:unexpected-character) src/content/services/modernization.mdx:55:22The rule Every page compiles to static HTML before anything is checked; content that won’t compile stops the line, with the file and line. Type check passed this one. Only the build caught it.
A slip in the copy can’t take a page down, because it can’t get there.
Add an analytics snippet: Stopped at Verify. Not merged. Passed: Types, Unit tests, Build. Stopped it: Verify. Not run: Accessibility and journeys.
$ pnpm verify ✗ 2 problem(s): about/index.html: script not allowed (only ld+json, speculationrules and the /_astro/ entry module): <script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"> about/index.html: references third-party script origin https://www.googletagmanager.comThe rule A page may run one script: the site’s own entry module. No third-party script origins, except the anti-spam check on the contact page.
“No tracking” is a rule the build enforces, not a promise.
Add a 3 KB library to every page: Stopped at Verify. Not merged. Passed: Types, Unit tests, Build. Stopped it: Verify. Not run: Accessibility and journeys.
$ pnpm verify ✗ 1 problem(s): entry /_astro/Enhance…js + static imports is 4463 B gzip, over the 2048 B budgetThe rule The one script a page runs at load stays within 2,048 bytes gzip, imports included. Anything bigger loads later, on intent.
Pages stay quick on a mid-range phone because the build refuses the bytes.
Drop an image’s alt text: Stopped at Accessibility and journeys. Not merged. Passed: Types, Unit tests, Build, Verify. Stopped it: Accessibility and journeys.
$ pnpm test:e2e ✘ [desktop-light] › tests/e2e/a11y.spec.ts:29:3 › axe: /about/ ✘ [desktop-dark] › tests/e2e/a11y.spec.ts:29:3 › axe: /about/ ✘ [mobile-reduced-motion] › tests/e2e/a11y.spec.ts:29:3 › axe: /about/ + "image-alt: 1 node(s) · Images must have alternative text",The rule A real browser opens every template in light, dark and on a phone with reduced motion, and runs axe’s WCAG A and AA rules. Verify passed this one; axe did not.
Nobody using a screen reader meets an unlabelled image.
ExampleEach log is real output, trimmed: we made the change to a copy of this site and ran the checks on .
Islands, loaded on intent
Rich interaction without the usual cost: one small script runs at load, and nothing more is fetched until someone reaches for it or scrolls to it.
HTML and CSS first
Every feature works as native HTML and CSS: links, forms, details, popover. Motion is CSS, off under reduced motion.
One small entry
A single module at load, held to 2,048 bytes. It waits for a reason to load anything more.
Loaded on intent
Islands are fetched on the first hover, focus, press or key, or as they scroll near. Until then they cost nothing.
Heavy work off the main thread
Per-frame drawing runs in a Web Worker on an OffscreenCanvas, so scrolling and typing stay free.
No framework runtime and no single-page router. The terrain lab shows the off-main-thread renderer on its own.
Static at the edge
Pages are files, served close to the reader. Of the few parts that have to be live, the quote form works without JavaScript and stores every request before it is delivered.
Static pages
Most pages are prerendered at build time and served as static files by Cloudflare Workers.
A few live routes
The contact form, the privacy choices page and the WordPress tracker with its data files render per request; everything else is a static file.
Stored first
A quote request is validated, then saved to a Cloudflare D1 database before it goes anywhere else.
Then delivered
The CRM and the studio inbox are updated once the page has answered you; a daily job retries anything that failed.
Private by default
- No cookies of our own, and nothing in your browser’s storage.
- No analytics scripts, ads or trackers. No pixels, no session recording, no embedded third-party content.
- Page counts on our own server: views per page per day, with no cookies, no script and no personal data.
- Fonts from our own domain, so loading a page doesn’t contact a font service.
- Global Privacy Control honored, and published as a support resource for browsers to read.
- One third-party script: Cloudflare Turnstile, an anti-spam check that loads only on the contact page.
FIRN, one set of tokens
Color, type, spacing, radius and motion live in one token file. Light and dark are the same names with different values, and every page inherits them.
- Whiteout#F7F9FBPage, light
- Firn#EEF2F6Tinted chapter
- Night Ridge#0A0D12Dark chapter
- Deep Ice#0D1A2BClosing band, footer
- Glacier#0B5FD6The one accent
- Alpenglow#D9481FOne signal per view
- Inter Tight · display
- Inter · text
- Radius 2 / 6 / 12 / 20
- Motion in CSS
- Off under reduced motion
Start a project
Hold your build to the same standard.
We reply within one business day, with questions or a time to talk.
Pittsburgh, PA · Since 2015 · First step: a 30-minute call