← Back to library

Agency

Agent Foundry

Dark agency one-pager with a live node-graph panel, numbered chapters and a hard red accent.

3 tools★ 95
#dark#graph#services
ClaudeCursorv0

The prompt

Prompt
# Agent Foundry — Dark Agency One-Pager

## What you're building

A single-page site for `{{brand}}`, a studio that builds conversion websites and working AI agents for small and mid-sized businesses. It reads like an instrument panel: near-black ground, mono system labels, a chapter counter (`01 / 05`) that advances as you scroll, and a live node-graph rail on the right that shows the visitor's own progress through the argument. The distinctive idea: **the site is itself the proof** — it doesn't claim the studio builds working systems, it runs one in front of you and invites inspection.

## Art direction

Mood: a foundry control room at night — deep green-black metal, one hot red that means *act*, one signal lime that means *system*.

| Token | Hex | Use |
|---|---|---|
| `--void` | `#050907` | Page ground, hero left column, footer |
| `--panel` | `#09100D` | Section ground, the default surface |
| `--panel-2` | `#101412` | Cards, chapter panels |
| `--panel-3` | `#121713` | Nested cards, form ground |
| `--panel-4` | `#171D19` | Hover state of any panel |
| `--panel-5` | `#18211D` | Raised card, node-graph panel body |
| `--red` | `#F0523D` | Primary CTA fill, chapter tab active, node pulse, eyebrow labels |
| `--lime` | `#C9F05B` | System/status signal, skip link, active node, mono accents |
| `--teal` | `#0E6B62` | Node graph edges, inactive connection lines |
| `--bone` | `#F2EFE7` | Primary type |
| `--bone-dim` | `#C9C4BA` | Body secondary, mono labels |
| `--bone-mid` | `#DED9CF` | Card body copy |
| `--on-accent` | `#101311` | Type set on `--red` or `--lime` fills |

Type stack — one sans, one mono, no serif anywhere.

- **Display (H1)** — `"Instrument Sans", Arial, sans-serif`, weight 700, `clamp(32px, 3.2vw, 46px)`, `letter-spacing: -0.02em`, `line-height: 1.06`. The cap is 46px, not 64px, and the reason is arithmetic: the H1 lives in the hero's left column, which is 47% of the viewport less a 6vw inset — about 590px of usable width at 1440. At 46px the supplied headline's longest line measures ≈455px and clears it; at 64px it would need 630px and wrap to four lines, breaking the two-line rule the design depends on. Give the H1 `max-width: 30rem` so it cannot creep wider than the space that was measured.
- **Section heading (H2)** — same family, weight 700, `clamp(27px, 2.6vw, 40px)`, `letter-spacing: -0.015em`, `line-height: 1.12`, `max-width: 24ch`. Headings break over two lines at an explicit `<br />`, mid-sentence, with no space after the break — the exact breaks are in **Copy**. Keep them; the hard break is the voice.
- **Card heading (H3)** — weight 700, `clamp(20px, 1.6vw, 24px)`, `line-height: 1.3`.
- **Body** — `"Instrument Sans"`, weight 400, `18px`, `letter-spacing: 0`, `line-height: 1.5`, colour `--bone` (cards use `--bone-mid`).
- **Label / system text** — `"IBM Plex Mono", monospace`, weight 500, `11px`, `letter-spacing: 0.16em`, uppercase. This is the site's voice for chapter numbers, section eyebrows, node names, status strings, and field labels.

Buttons: **`border-radius: 0`** everywhere — hard corners are the signature. Primary = `--red` fill, `--on-accent` label, `padding: 16px 28px`, sans weight 700. Ghost = transparent with `1px solid rgba(242,239,231,0.22)`. Skip link = `--lime` fill.

## Copy

Every string the page renders, verbatim. `{{brand}}`, `{{project-01}}`…`{{project-06}}`, `{{type}}` and `{{metric}}` stay as placeholders — they would name a real business, a real client or a real result. Everything around them is written. **Never invent a client name, a logo, or a number.**

### Chrome

| Slot | String |
|---|---|
| Skip link | `Skip to services` |
| Nav wordmark | `{{brand}}` |
| Nav links | `Services` · `Work` · `Process` |
| Nav button | `Plan My Build` |
| Chapter counter | `01 / 05` (digits only; the slash and total are literal) |
| Status strip | `{{brand}} / SYSTEM LIVE` |

### Node rail

Five nodes, top to bottom. The label is the accessible name's second half; the number is its first.

| # | Label |
|---|---|
| 01 | `Recognition` |
| 02 | `Diagnosis` |
| 03 | `Evidence` |
| 04 | `Demonstration` |
| 05 | `Commitment` |

Accessible name pattern: `Chapter 03, Evidence`.

### 1 · Hero

| Slot | String |
|---|---|
| Eyebrow (`--red` mono) | `WEBSITES & WORKING AGENTS` |
| H1 (two lines, hard break) | `Demand arrives.` ⏎ `Most of it leaks.` |
| Body | `We build the site that catches it and the agent that answers it — then hand you both, running.` |
| Ghost button | `See What We Build` |
| Media cue | `Enter the foundry` |

### 2 · Services

| Slot | String |
|---|---|
| Eyebrow | `WHAT WE BUILD` |
| H2 (two lines, hard break) | `We build three things.` ⏎ `Each one closes a leak.` |

| No. | H3 | Body | Capability list |
|---|---|---|---|
| `01` | `Conversion Sites` | `A site built around a single decision. Structure, copy and load speed tuned so a visitor who arrived ready to buy actually can.` | `Structure & copy` · `Design system` · `Build & deploy` · `Speed budget` |
| `02` | `Working Agents` | `An agent that answers, qualifies and routes — inside your inbox, your forms, your calendar. Deterministic where it must be, with a person on the hook.` | `Intake & triage` · `Routing rules` · `Human handoff` · `Logging & review` |
| `03` | `Operations` | `The part nobody sells you: someone owns the thing after launch. Changes, monitoring, and a monthly read on what the system actually did.` | `Monitoring` · `Monthly changes` · `Incident response` · `Performance report` |

### 3 · Selected work

| Slot | String |
|---|---|
| Eyebrow | `SELECTED WORK` |
| H2 (two lines, hard break) | `Inspect the interface.` ⏎ `Not a mockup of one.` |
| Intro | `Six builds, each one still running. Stack, role and status are listed as shipped, not as pitched. Where a number appears it carries its source beside it.` |
| Caption bar (per block) | `{{project-0N}} / {{type}}` |
| Spec row labels | `stack:` · `role:` · `status:` |
| Inspect link | `Inspect` |

Project H3s and descriptions name a **function**, never a client. All six are written out below — nothing here is a template to be extended. Every block's `stack:` value is `{{stack}}` and every block's `status:` value is `Live since {{date}}`; the `role:` values differ and are given per block.

| # | H3 | Description | `role:` |
|---|---|---|---|
| 01 | `Booking front end` | `A booking flow rebuilt around one decision instead of five. The old path asked for a date before it said what anything cost; the new one answers the price question first and collects the date last. Live, and still the only page that takes payment.` | `Structure, copy, build` |
| 02 | `Quote intake` | `A quote request that returns a figure in the same session instead of a promise to call back. It replaced a contact form whose only output was an email that queued behind everything else in the inbox. Live, and the first surface the sales desk opens each morning.` | `Structure, build, agent` |
| 03 | `Field service dispatch` | `A dispatch board that turns an inbound job into an assigned van without a phone call in the middle. It replaced a whiteboard that was photographed and texted around at seven each morning. Live, and running longer without a change than anything else on this page.` | `Structure, build, operations` |
| 04 | `Membership portal` | `A members' area where renewing, pausing and updating a card are three buttons on one page. It replaced a login that could show you an invoice but never let you act on it. Live, and it now absorbs the three requests that used to arrive as support mail.` | `Design system, build` |
| 05 | `Trade order desk` | `An ordering surface for account customers who already know the part number and want to be out in under a minute. It replaced a public catalogue that made trade buyers shop like retail. Live, and it is where repeat orders now go.` | `Copy, build, speed budget` |
| 06 | `Referral routing` | `A referral path that records who sent whom and closes the loop back to them when the work is done. It replaced a spreadsheet that captured the first half of that and forgot the second. Live, and it runs without anyone maintaining a list.` | `Structure, build, routing rules` |

**`Inspect` link destination.** Each block's link points at `{{project-0N-url}}` and opens in a new tab (`target="_blank" rel="noopener noreferrer"`). If no URL is supplied for a block, **omit the link element entirely** for that block — do not render `href="#"`, a disabled link, or a `<button>` that does nothing. A dead control on a page whose argument is "inspect the real thing" is the worst possible defect.

### 4 · Case file

| Slot | String |
|---|---|
| Eyebrow | `CASE FILE 001 / {{type}}` |
| Framing line | `One build, taken apart in five moves. Every figure below is {{metric}} until you can see where it came from.` |

| Block | Mono heading | H3 (the finding, one sentence) |
|---|---|---|
| 01 | `01 / CONSTRAINT` | `The enquiries were arriving; nobody was answering them inside a day.` |
| 02 | `02 / DECISION` | `Answer first, qualify second — and never let the qualifying step be a form.` |
| 03 | `03 / BUILD` | `One intake surface, three routing rules, and a person on every handoff.` |
| 04 | `04 / EVIDENCE` | `Response time moved from {{metric}} to {{metric}}, measured in the same log both times.` |
| 05 | `05 / STATUS` | `Running unattended since launch, with a monthly review that has changed it twice.` |

Block bodies, in order:

1. `Enquiries arrived through four surfaces and landed in one shared inbox nobody owned. Most were answered eventually. "Eventually" was the problem: by the time a reply went out, the person had already been answered by someone else. Nothing was broken, which is why it had survived {{metric}}.`
2. `We did not add a qualification form. A form is a toll gate that punishes the people most likely to buy. Instead the first response goes out immediately and unconditionally, and the qualifying happens inside that exchange, where a human can read it and override the routing at any point.`
3. `One intake surface replaced the four. Three routing rules — written in plain language on a page the client can read — decide where an enquiry goes. Every route ends at a named person, and no enquiry advances past that person without an explicit approval. The rules changed twice in the first month.`
4. `Median first response moved from {{metric}} to {{metric}}. Both figures come from the same mail log, measured the same way, over comparable periods — we did not switch instruments between the before and the after. Conversion moved too, by {{metric}}, but the response time is the number we stand behind.`
5. `It has run without intervention since launch. The monthly review has produced two changes: one routing rule retired because it never fired, and one added after a category of enquiry appeared that nobody had predicted. Both took under an hour. That is what "operate" means on the build sequence.`

Blocks 03 and 04 each carry an image. Case-file images have no project id, so their caption bars do **not** use the `{{project-0N}}` pattern — they are literal:

| Image | Caption bar |
|---|---|
| Block 03 | `CASE FILE 001 / INTAKE SURFACE` |
| Block 04 | `CASE FILE 001 / RESPONSE LOG` |

### 5 · Working agent flow

| Slot | String |
|---|---|
| Eyebrow | `WORKING AGENT` |
| H3 | `Run it yourself.` |
| Disclaimer | `A deterministic prototype running in your browser. No model call, no network request, nothing leaves this page.` |

| Step | Label | Description |
|---|---|---|
| 1 | `Intake` | `Takes the enquiry in whatever shape it arrives.` |
| 2 | `Qualify` | `Asks the two questions that decide whether this is real.` |
| 3 | `Route` | `Picks the destination from rules you can read.` |
| 4 | `Human check` | `Holds here. Nothing moves until a person approves it.` |
| 5 | `Handoff` | `Writes the record and passes it on, with the reasoning attached.` |

Every control and every state string the flow renders:

| Slot | String |
|---|---|
| Start button (flow not yet run) | `Run the flow` |
| Restart button (flow complete) | `Run it again` |
| Human-check approve button | `Approve & continue` |
| Step status — before the flow starts | `IDLE` |
| Step status — step currently executing | `RUNNING` |
| Step status — step 4 holding for approval | `AWAITING APPROVAL` |
| Step status — step 4 after approval | `APPROVED` |
| Step status — any completed step | `DONE` |

The start and restart buttons are the same control in two label states; it sits below the five steps, styled ghost (not `--red` — it is a demonstration, not a conversion point). The approve button appears **inside** step 4 and only while that step reads `AWAITING APPROVAL`.

### 6 · Build sequence

| Slot | String |
|---|---|
| Eyebrow | `HOW IT GOES` |

| No. | H3 | Body | Duration |
|---|---|---|---|
| `01` | `Diagnose` | `We find where demand is arriving and where it stops. Nothing is designed yet.` | `Week 1` |
| `02` | `Design` | `Structure and copy first, surfaces second. You approve the argument, not a mood board.` | `Weeks 2–3` |
| `03` | `Build` | `Site and agent built together, because the agent is what the site is for.` | `Weeks 4–6` |
| `04` | `Operate` | `We run it, watch it, and send you one honest page every month.` | `Ongoing` |

### 7 · Project intake

| Slot | String |
|---|---|
| Eyebrow | `PROJECT INTAKE / 01` |
| H2 (two lines, hard break) | `What is actually` ⏎ `costing you the sale?` |
| Framing line | `Tell us the bottleneck. If we are not the right build for it, we will say so and point you somewhere.` |

Trust rows (mono `label: value`):

| Label | Value |
|---|---|
| `response time` | `one business day` |
| `what happens next` | `a 25-minute call, no deck` |
| `what it costs to ask` | `nothing` |

Form:

| Field | Label | Required | Detail |
|---|---|---|---|
| Build type | `What are you building?` | **yes** | Radio cards: `Conversion site` · `Working agent` · `Both` · `Not sure yet` |
| Bottleneck | `Where does it break down today?` | **yes** | `<textarea>`, placeholder `Enquiries pile up over the weekend and nobody gets to them until Tuesday.` |
| Name | `Your name` | **yes** | — |
| Email | `Work email` | **yes** | — |
| Company | `Company` | no | — |
| Budget | `Budget range` | no | Select: `Under 10k` · `10k–25k` · `25k–60k` · `60k+` · `Tell me what it should be` |
| Submit | `Plan My Build` | — | — |
| Line beneath submit | `Goes to a person, not a queue. One reply, one business day.` | — | — |

Four fields are required: build type, bottleneck, name, work email. Company and budget are optional and carry no required marker — the form asks for the bottleneck before the budget on purpose, and making the budget mandatory would undo that.

Form states:

| State | String |
|---|---|
| Empty / disabled | Template: `Add {field} to send this.` — see the four strings below |
| Loading | Button label `SUBMITTING` |
| Error | `That email address did not parse. Everything else you typed is still here.` |
| Success heading | `Received.` |
| Success body | `Reference {{reference_id}}. A person reads this within one business day and replies to the address you gave.` |
| Success link | `Read the case file` |

The empty-state hint names the **first** required field still missing, in the form's own top-to-bottom order. There are four possible strings and they are all supplied — nothing is generated at runtime:

| First missing required field | Hint string |
|---|---|
| Build type | `Add a build type to send this.` |
| Bottleneck | `Add the bottleneck to send this.` |
| Name | `Add your name to send this.` |
| Work email | `Add your work email to send this.` |

### Alt text and non-visible labels

Alt text is copy: it is what a screen-reader user reads instead of the image, so it is specified here and not left to the builder. Eleven strings, one per named asset.

| Asset | Alt / label |
|---|---|
| Hero media poster | `An industrial workshop interior at night, machine bays lit from overhead.` |
| Hero video (`aria-label`, when present) | `Looping footage of the workshop interior at night.` |
| Work block 01 | `A booking screen that shows prices before it asks for a date.` |
| Work block 02 | `A quote form returning a priced figure on the same screen.` |
| Work block 03 | `A dispatch board assigning inbound jobs to named vans.` |
| Work block 04 | `A member account page with renew, pause and update-card controls together.` |
| Work block 05 | `A trade order screen that takes a part number and a quantity.` |
| Work block 06 | `A referral record showing who sent an enquiry and how it ended.` |
| Case-file image, block 03 | `The single intake surface that replaced four separate ones.` |
| Case-file image, block 04 | `A response-time log with the before and after periods side by side.` |
| Logo row (`aria-label` on the row) | `Tools this build runs on` |

The three logo placeholders themselves are decorative: `alt=""`, and the row carries the label above. Alt text describes what the interface *does* — never "screenshot of website", never the client's name, never a name the prompt did not supply.

### 8 · Footer

| Slot | String |
|---|---|
| Wordmark | `{{brand}}` |
| Column 1 heading / links | `BUILD` — `Conversion sites` · `Working agents` · `Operations` |
| Column 2 heading / links | `PROOF` — `Selected work` · `Case file` · `Agent flow` |
| Column 3 heading / links | `CONTACT` — `Plan my build` · `{{email}}` · `{{location}}` |
| Logo row label | `RUNS ON` (three logo placeholders beneath) |
| Status line | `BUILD {{build_id}} / DEPLOYED {{deploy_date}} / UPTIME {{uptime}}` |

## Layout

This is a long page — roughly **fifteen to eighteen screens** at 1080px, depending on your copy and viewport. Treat that as a sanity figure, not a target: never pad whitespace to hit a screen count. What matters is the proportion. Selected work must dominate the scroll (it is the longest chapter by a wide margin), the hero is exactly one viewport, and the working agent flow is the shortest section on the page because a demonstration should be over quickly.

### Section → chapter mapping

Eight layout sections, five chapters. The mapping is fixed — do not derive it:

| Chapter | Node label | Sections it covers | Anchor |
|---|---|---|---|
| `01` | `Recognition` | 1 Hero | `#recognition` |
| `02` | `Diagnosis` | 2 Services | `#diagnosis` |
| `03` | `Evidence` | 3 Selected work, 4 Case file | `#evidence` |
| `04` | `Demonstration` | 5 Working agent flow, 6 Build sequence | `#demonstration` |
| `05` | `Commitment` | 7 Project intake, 8 Footer | `#commitment` |

A chapter is one `<section>` wrapper carrying the anchor id and the observer target; where a chapter covers two layout sections they are siblings inside that wrapper. The chapter counter therefore reads `03 / 05` across both Selected work and Case file, and clicking node `03` lands on Selected work.

**Every anchor destination on the page** — there are no others, and nothing links to `#`:

| Control | Target |
|---|---|
| Skip link (`Skip to services`) | `#diagnosis` — chapter 02, which *is* Services |
| Nav link `Services` | `#diagnosis` |
| Nav link `Work` | `#evidence` |
| Nav link `Process` | `#demonstration` |
| Node rail `01`–`05` | `#recognition` · `#diagnosis` · `#evidence` · `#demonstration` · `#commitment` |
| Mobile chapter chips | same five anchors |
| Nav button / form submit | not anchors — the nav button scrolls to `#commitment`, the submit posts the form |
| Success link (`Read the case file`) | `#evidence` |

**Ground colours.** `--void` is the hero's left column, the fixed nav, and the footer. **`--panel` is the ground of every section between them** — Services through Project intake sit on it, and it is what the page body is painted with, so a section that sets no background of its own is already correct. Card and panel surfaces layer up from there (`--panel-2` → `--panel-5`) as the token table describes.

**Fixed nav** — left: 28px square `--red`-bordered mark + wordmark in mono label style. Right: the three nav links then `Plan My Build` as a square `--red` button. Solid `--void` with a bottom hairline from 0px; no transparency.

**Persistent chrome** (both fixed, both part of the design):
- **Chapter counter** — fixed, top-left of the content column, mono, `01 / 05`, current number in `--bone`, total in `--bone-dim`. Increments with the active chapter. **This fixed counter is the only one that renders.** It is already on screen over the hero, so the hero's left column does not repeat it — one counter on the page, never two.
- **Status strip** — bottom-right, mono, on `--panel-5` with a 1px `--red` top border and a blinking `--lime` dot.

1. **Hero — split, exactly `100vh` (`100dvh` on mobile), `overflow: hidden`.** The hero is one viewport and nothing more; it is not a scrolling wrapper and it does not contain the node rail. It is the one full-bleed section: it ignores the `23rem` right gutter, because the rail is *meant* to float over the hero media there and the media has nothing to collide with. Two columns, `grid-template-columns: 47fr 53fr`. Left: `--void`, vertically centred at a 6vw inset — mono eyebrow in `--red`, H1 over two hard-broken lines, one body paragraph, one ghost button. **No chapter counter in this column**; the fixed one is already over it. Right: full-bleed hero media (an industrial interior — see **Assets**) with a `--void` gradient scrim on its left edge so the split never reads as a seam. Bottom-centre of the media: the mono cue above a thin scroll indicator.
2. **Node-graph rail — `position: fixed`, for the whole page, not just the hero.** It is the page's answer to "where am I and how much is left", so it cannot leave with the hero. `right: 2rem`, vertically centred, `width: 320px`, `z-index` above content and below the nav. The layout reserves this space, and the reserve is a right **gutter**, not a narrower centred column — centring a narrower wrapper just moves the collision. On viewports ≥ 1280px the page container takes `padding-right: 23rem` (368px), and sections keep `max-width: 1230px` *inside* that padding. The rail's left edge then sits at `100vw − 352px` while content ends at `100vw − 368px`: a 16px clearance that holds at 1280px and only grows above it. Below 1280px the rail collapses to the mobile chapter strip described under **Mobile**. Inside the panel: 5 labelled nodes stacked vertically, connected by `--teal` edges, each with a status dot. Each row is dot on the left, then the mono number and label to its right, inside the 320px panel — there is no space to the left of a dot and nothing is ever placed there. The active node gets a `--red` dot fill, and **its label chip changes in place**: `--panel-5` background, `--bone` type, a 2px `--red` left border where it meets the dot. No tooltip, no popover, no repositioning — the chip does not move when it becomes active, because a row that shifts sideways on scroll is exactly the twitch this rail exists to avoid. Clicking a node scroll-jumps to that chapter's anchor.
3. **Services.** Mono eyebrow, H2 broken over two lines, then a 3-up card grid: each card is `01`/`02`/`03` in mono, an H3, the body, and a 4-item capability list in mono. Cards on `--panel-2`, 1px `rgba(242,239,231,0.08)` border, hover lifts to `--panel-4` with the border going `--red`.
4. **Selected work.** The longest chapter — it should read as roughly a third of the page. Mono eyebrow, H2, the intro line, then **six** stacked project blocks, one image each. Each: full-width screenshot at 16:10 in a `--panel-2` frame with a mono caption bar above it, a 2-column text row under it — left: the H3 and description from **Copy**, right: the three `label: value` mono spec rows — and a ghost `Inspect` link.
5. **Case file.** Mono eyebrow, the framing line, then five numbered blocks in a single column. Each is a mono heading + H3 + body, all from **Copy**. Blocks 03 and 04 carry an image each — two in the section. A left `--red` 2px rail runs the height of the column with a marker at each block.
6. **Working agent flow.** The shortest section on the page. Mono eyebrow, H3, the disclaimer line. Then a horizontal 5-step flow. Each step is a `--panel-3` box with a mono label, a one-line description, a mono status line carrying one of the five state strings from **Copy**, and a `--teal` connector arrow. Below the row sits the ghost start control (`Run the flow` / `Run it again`). The `Human check` step is visually emphasised — `--lime` border — because that's the trust argument, and **it blocks**: see the blocking clause under **Conversion architecture**.
7. **Build sequence.** Mono eyebrow, then a 4-up numbered grid. Each = mono number in `--red` at 32px, H3, the body, and the duration as a mono line. Thin `--teal` connecting line behind the row on desktop.
8. **Project intake.** Two columns: `grid-template-columns: minmax(0, 0.82fr) minmax(0, 1fr)` with a `4rem` gap, inside the same reserved-gutter wrapper as every other section. No fixed pixel columns — a pair of hard-coded widths summing to 1230px collides with the 320px rail on every viewport under about 1650px and drops the rail on top of the form. Left column: mono eyebrow, H2 as a question over two hard-broken lines, the framing line, and the three trust rows as mono `label: value`. Right column: the form on `--panel-3` — a radio group of build types as square selectable cards, the bottleneck `<textarea>`, name, email, company, budget select, and the primary submit with a mono line beneath it.

**Footer** — `--void`, mono throughout: wordmark, the three link columns, a row of three logo placeholders under the `RUNS ON` label, and the status strip's full version.

## Conversion architecture

- **Hook** — the split hero withholds a pitch. The left side names the problem in the visitor's own language (demand arriving and leaking away); the right side shows machinery. The chapter counter promises finite length, which matters enormously on a page this long.
- **Proof / credibility device** — the page is an evidence ladder, which is why the chapters are named `Recognition → Diagnosis → Evidence → Demonstration → Commitment`. Proof escalates rung by rung:
  1. **Services** — what we do. Cheapest claim, so it goes first and stays short.
  2. **Selected work** — what it looks like. Six inspectable interfaces, not mockup renders.
  3. **Case file** — what changed, and how it was measured. Constraint → decision → build → evidence → status.
  4. **Working agent flow** — a thing the visitor can actually operate in the browser. This is the strongest asset on the page; it goes **before** the intake form, never after.
  Use `{{metric}}` placeholders for every result and label each with its source. Never invent a number, a client, or a logo.
- **The Human check step blocks.** This is the page's entire trust argument, so it must be a real gate, not a highlight. When the flow reaches step 4 it **stops**: steps 5 and beyond do not run, the step's status line reads `AWAITING APPROVAL`, and an `Approve & continue` button appears inside the step. Nothing advances until that button is pressed — no timeout, no auto-continue, no "it proceeds but shows a badge". On press, the status flips to `APPROVED`, the `--lime` border stays, and step 5 runs. Restarting the flow re-arms the gate. A visitor who walks away mid-flow finds it still waiting, which is the point being made.
- **Objection handling** — each answered structurally, not in an FAQ:
  - *"AI agency talk is vapour."* → the agent flow runs, and its own disclaimer states plainly what it is and isn't.
  - *"You'll sell me tools I don't need."* → the services H2 leads with the constraint, and the intake form asks for the bottleneck before it asks for a budget.
  - *"I can't see your actual work."* → six live interfaces with stack / role / status specs, framed as "inspect the interface, not a mockup".
  - *"This will take months."* → the four-step build sequence, each step carrying a duration.
  - *"What does contacting you cost me?"* → the three trust rows sitting beside the form, above the submit.
- **CTA placement and repetition** — two hard conversion points, many navigation affordances. The ratio is intentional for a considered-purchase service:
  - `Plan My Build` (red) in the nav from 0px, and again as the intake form's submit.
  - `See What We Build` (ghost) in the hero — deliberately *not* a conversion CTA, because at screen one nobody is ready to buy.
  - Node-rail chapter tabs act as micro-CTAs throughout the scroll.
  - On mobile, a sticky bottom `Plan My Build` bar appears after chapter 3.
- **Form states**
  - *Empty* — submit disabled with a mono hint naming the missing field.
  - *Loading* — button `aria-busy`, label → `SUBMITTING`, fields disabled, a `--lime` progress hairline under the panel.
  - *Error* — `role="alert"` above the form naming the field, focus moved there, every typed value preserved, submit re-enabled.
  - *Success* — the form panel is replaced in place by a `--panel-5` confirmation carrying a mono reference id, the response-time commitment, and one next-step link. No redirect, no scroll jump.
- **Exit-risk** — on a page this long the chapter counter and node rail must always answer "where am I and how much is left". No modal interrupts, no exit-intent popup, no newsletter gate, no chat bubble.

## Motion

Mechanical, snappy, never decorative. No easing softer than `cubic-bezier(0.2, 0.8, 0.2, 1)`.

**Node-graph idle** — the five nodes run a continuous pulse cycle: a `--lime` packet travels along each `--teal` edge top-to-bottom, 1.4s per hop, 0.35s gap, looping. Each node dot breathes `opacity 0.5 → 1` over 2.2s, offset by index × 0.4s. The active node's dot holds a solid `--red` fill with a 1px expanding ring (`scale 1 → 2.2`, `opacity 1 → 0`, 1.6s, infinite). SVG only, ≤ 40 animated elements, `will-change` on nothing.

**Chapter transitions** — driven by `IntersectionObserver`, not scroll math. Two constructions below; use both. They are chosen because their behaviour is unambiguous, not because the alternatives are broken.

"Observe at 50% of the viewport" is commonly implemented as `rootMargin: "-50% 0px -50% 0px"`, which leaves the observer root with **zero height**. Use a band with real height instead — a root with measurable height has one obvious intersection semantics, and a zero-height one does not:

```js
const io = new IntersectionObserver(onChapters, {
  rootMargin: "-50% 0px -49% 0px",   // a ~1%-tall band across the viewport middle
  threshold: 0,
});
```

And **resolve the active chapter from geometry inside the callback, not from entry order.** This is the half that actually protects you. Entry order is not sorted by position: a fast fling delivers several entries in one callback in arbitrary order, so taking the last `isIntersecting` entry selects whichever chapter the observer happened to report last, which on a fling is routinely the wrong one. Instead, on every callback read the current positions of all five chapter wrappers and pick the one whose box contains the viewport midpoint:

```js
const mid = window.innerHeight / 2;
let active = 0;
chapters.forEach((el, i) => {
  const r = el.getBoundingClientRect();
  if (r.top <= mid && r.bottom > mid) active = i;
});
```

The observer is then only a cheap trigger for "something crossed the middle, recompute" — it never decides *what* is active. That is why the two constructions belong together: with the resolver in place, the observer's exact firing pattern stops being load-bearing. This also makes hash navigation and back/forward correct for free: fire the same resolver once on mount and on `hashchange`.

On chapter change: the counter digit does a 0.22s vertical slot-roll (old digit up and out, new in from below, `cubic-bezier(0.2, 0.8, 0.2, 1)`); the node rail's active state moves with a 0.3s `--red` fill crossfade and the edge above it fills `--lime` left-to-right in 0.4s; the chapter's H2 and eyebrow rise `y 18px → 0` with a **0.07s stagger**, 0.5s each.

**Section reveals** — cards fade + rise 14px with 0.06s stagger per row, 0.45s. Numbers (`01`, `02`…) fade 0.15s ahead of their card. Work-block screenshots do a 0.5s `clip-path: inset(0 100% 0 0)` wipe to `inset(0)` on entry.

**Micro** — button hover: `--red` lightens 6% in 0.15s, no transform. Panel hover: background `--panel-2 → --panel-4` in 0.18s and border to `--red`. Status-strip dot blinks 1.1s on / 0.4s off. Mono labels get a 0.12s letter-spacing tighten on link hover.

**`prefers-reduced-motion: reduce`** — **scroll-triggered reveals are removed entirely, not shortened.** No fade, no 100ms opacity transition, no stagger: every section renders at `opacity: 1` with no transform from first paint, and the reveal `IntersectionObserver` is never constructed. A 100ms fade is still a scroll-triggered reveal and still fails the check. Alongside that: the node graph renders static with the current chapter's node filled and no packets, pulses, or rings, and stays fully clickable; counter digits swap instantly; screenshot wipes are gone; the status dot stops blinking and renders solid `--lime`. Hover colour changes stay — they are state feedback, not motion. The chapter observer keeps running, because the counter and rail are navigation, not decoration.

## Build notes

- **Stack** — framework-neutral. Any React setup works (Vite, Next, Remix, plain CRA-style tooling) and so does no framework at all; nothing on this page needs one. What is fixed is the dependency floor: no GSAP, no Three.js, no scroll library, no animation library — `IntersectionObserver` for chapters and reveals, CSS keyframes for the node graph, plain component state for the form and the agent flow. Keeping the dependency list this short is part of the pitch. The one place the framework leaks is fonts; see **Fonts** below for the Next-specific route.
- **Assets** — this clause is as strict as the no-client-names clause. The page renders **twelve image boxes** and, optionally, one video. Twelve is the sum of the table below (1 + 6 + 2 + 3); the hero video is not one of them, because its default is to be omitted and render the poster instead. None of this media may be invented content:

  | Asset | Count | Aspect | Intrinsic size | Default |
  |---|---|---|---|---|
  | Hero media poster | 1 | 16:9 | 1920 × 1080 | Solid `--panel-2` block with a 1px `--red` corner tick, no text |
  | Hero video | 1 | 16:9 | 1920 × 1080 | **Omit the `<video>` entirely** and render the poster `<img>`; add the video only when `{{hero_video}}` is supplied |
  | Project screenshots | 6 | 16:10 | 1600 × 1000 | Solid `--panel-2` block, 1px `rgba(242,239,231,0.08)` border, mono `{{project-0N}}` centred at 30% opacity |
  | Case-file images | 2 | 3:2 | 1400 × 933 | Same flat block and border as the project screenshots, but the centred mono text is `INTAKE SURFACE` (block 03) and `RESPONSE LOG` (block 04) at 30% opacity — **not** `{{project-0N}}`, which case-file images do not have |
  | Partner / stack logos | 3 | 15:4 | 120 × 32 | Empty `--panel-3` rectangle, mono `LOGO` at 30% opacity |

  **The default treatment renders no `<img>` at all** — a flat block is a `<div role="img">` carrying the alt string from **Copy** as its `aria-label`. So the CLS requirement is stated in terms both cases satisfy: **every media box reserves its space from CSS alone, before anything loads.** Give each box `width: 100%` and the `aspect-ratio` from the table (`16 / 9`, `16 / 10`, `3 / 2`, `15 / 4`); the intrinsic sizes above exist to fix that ratio and to size a real asset when one is supplied. When `{{…}}` supplies a real file the box becomes an `<img>` that keeps the same `aspect-ratio` rule *and* adds matching `width`/`height` attributes — belt and braces, and the layout does not move at the swap. That is the only reason the dimensions are specified.

  Do **not** substitute inline-SVG wireframes, stock photography, generated UI mockups, or anything that could be mistaken for a real client's product. A flat block is the correct placeholder; the mono caption bar above each block is what tells the visitor what they are looking at.

- **Lazy-load** — the six work screenshots and two case-file images are below the fold: `loading="lazy"`, `decoding="async"`, explicit dimensions, AVIF + WebP where you have real assets. The hero video, when present, is `preload="metadata"` with the poster, and is replaced by the poster image entirely under 768px.
- **Performance budget** — LCP ≤ 2.2s (the LCP element is the H1, which is text). Initial JS ≤ 160KB gzipped. CLS 0 — every image box reserved per the table above. Total transferred under 2.5MB with images lazy; nothing above the fold may exceed 400KB.
- **No code splitting.** The agent-flow prototype is a few kilobytes of plain state in the same bundle — a dynamic import "that loads when its section is 400px away" is theatre in a single-page deliverable and buys nothing measurable. Ship it inline. If you need to defer anything, defer the below-fold *images*, which the lazy-load clause already covers.
- **Fonts** — no font files ship with this prompt, so do not write a `@font-face` block pointing at files you do not have. Load Instrument Sans 400/700 and IBM Plex Mono 500 from Google Fonts with `display=swap`. **How you load them depends on the framework, so pick the right one:**
  - Vite, plain HTML, or any non-Next setup: a `<link>` to `fonts.googleapis.com` in the document head, plus `<link rel="preconnect">` to `fonts.gstatic.com`.
  - **Next.js:** do not hand-write that `<link>` in a page — `@next/next/no-page-custom-font` lints it, and the framework has a better path anyway. Use `next/font/google` (`Instrument_Sans`, `IBM_Plex_Mono`), which self-hosts the files at build time, needs no `preconnect`, and removes the swap flash. Expose each as a CSS variable and use it in the type tokens.

  If the deliverable must be offline or self-hosted without a font pipeline, substitute the fallback stacks — `system-ui, -apple-system, "Segoe UI", Arial, sans-serif` and `ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace` — and say so; the layout is built to survive the swap because every size is set in px or clamp, not in ems of a specific face.
- **Accessibility**
  - The `--lime` "Skip to services" link is the first tab stop, visible on focus.
  - Focus ring: `2px solid var(--lime)` at `2px` offset. Required — `--red` on `--void` is not a reliable focus indicator.
  - Node-rail nodes are real `<button>`s with `aria-current="true"` on exactly one, and accessible names carrying both chapter number and name.
  - The chapter counter is `aria-hidden` (decorative). The real structure is a clean `h1`/`h2`/`h3` outline in order, with no skipped levels.
  - Every form field has a persistent visible `<label>`. The build-type cards are a `<fieldset>` + `<legend>` over real `<input type="radio">`, arrow-key navigable — not clickable `<div>`s.
  - Errors: `role="alert"` + `aria-invalid` + `aria-describedby`.
  - Screenshot alt text describes what the interface *does*, never "screenshot of website".
  - Verify `--bone-dim` on `--panel` and `--on-accent` on `--red` both clear 4.5:1. The 11px mono labels are the highest risk — raise them to 12px if contrast is marginal.
  - The agent-flow prototype must be operable by keyboard alone, with each step's state announced politely.
- **Mobile**
  - Hero split becomes stacked: poster image at 42vh on top, copy below, video replaced entirely by the poster.
  - **Below 1280px** the fixed node rail is replaced by a horizontal sticky chapter strip under the nav — five chips, scrollable, active chip auto-centred — and the `23rem` right padding goes away, so sections return to a plain centred `max-width: 1230px`. The changeover is at 1280px, not at a mobile breakpoint: the rail needs 320px plus a gutter, and below that it would sit on the content.
  - Services and build-sequence grids go 3-up / 4-up → 1.
  - Work blocks keep screenshots full-width and move the spec rows below the description.
  - Intake grid becomes one column with the form second; sticky `Plan My Build` bar appears after chapter 3.
  - All targets ≥ 44px.

## Acceptance checks

- The chapter counter advances past `01 / 05` on the first scroll. If it never moves, check the two things in **Motion**: the band `rootMargin` is in use, and the geometry resolver runs on every callback rather than the counter being set from entry order.
- The counter and node rail stay in sync with scroll position in both directions, including on fast flings (scroll from the footer to the hero in one throw and confirm the counter lands on `01`, not on an intermediate chapter) and on direct hash navigation to `#evidence`.
- Scrolling from Selected work into Case file leaves the counter on `03 / 05` — both are chapter 03.
- Clicking any node in the rail scrolls to that chapter and sets `aria-current` on exactly one node. **Let it settle before you judge it:** `html { scroll-behavior: smooth }` over a page this long (fifteen-plus screens) takes roughly **2–3 seconds** to travel from one end to the other, so a jump from `05` back to `01` is still moving well after the click. Read the counter and the `aria-current` node once the scroll has stopped, not during it.
- At 1440px the node rail sits entirely in the right gutter: it overlaps no heading, no card and no form field, on every section including Project intake. Reduce to 1300px and re-check; below 1280px the rail is gone and the chapter strip is under the nav. **One exception, and it is deliberate:** the hero is the page's single full-bleed section — it ignores the `23rem` gutter and its media runs under the rail on purpose. The rail floating over hero media is the intended composition, not a collision. Every *other* section must clear it.
- With `prefers-reduced-motion` on, every section is at full opacity in its final position with JavaScript disabled *and* enabled, no reveal observer is constructed, and the node graph is static but every node remains clickable.
- Every button and card on the page has `border-radius: 0` — no rounded corners anywhere except the status dot.
- Pressing `Run the flow` starts it; it then halts at `Human check` and does not reach `Handoff` until `Approve & continue` is pressed. Leaving it alone for a minute does not advance it. Its disclaimer text is present and legible, not hidden behind a tooltip.
- The H1 renders on exactly two lines at 1440px, 1280px and 1024px, and never overflows its column.
- All **twelve** image boxes are reserved before load: throttle to slow 3G, scroll the whole page, and CLS stays 0. The default flat blocks reserve via `aspect-ratio`, so this must hold with no image files present at all.
- Every image box has an accessible name from the **Alt text** table — a real `<img>` via `alt`, a default flat block via `role="img"` + `aria-label`. The three logo placeholders are the only ones with an empty name, and their row carries the label.
- Submitting the intake form with an empty required field announces the error, moves focus to that field, and preserves everything already typed.
- Lighthouse mobile: performance ≥ 88, accessibility 100, CLS 0; total page weight under 2.5MB with lazy images.
- The agent flow's start control reads `Run the flow` before the first run and `Run it again` after the flow completes, and restarting re-arms the `Human check` gate.
- Exactly one chapter counter renders. Scroll to the top and confirm the hero's left column does not carry a second one under the fixed chrome.
- No control on the page links to `#` or to a destination not in the anchor table under **Layout**. A work block with no `{{project-0N-url}}` shows no `Inspect` link rather than a dead one.
- No string on the page was written by the builder: every rendered word comes from **Copy** — including all six project descriptions, all six `role:` values, every alt text, every flow status string, and the four empty-state hints — or is a `{{placeholder}}`.

More like this