Back Office · office.temerarii.xyz
FRONT-DOOR.md

← all docs

The front door on temerarii.xyz

The short ask first, never the 21 questions. One record from the door to the deal. Every door,
every form, every branch and every event is a registry the office renders — the pages cannot
disagree with the build, and verify_intake refuses the ways they could.

The doors

Thirteen landing pages, one per ICP segment, at https://temerarii.xyz/go/<segment> (the map is

/go). Each is rendered from gtm.yaml icp.segments[]: the message, who it is for, the pain, the

trigger, the wedge — and the ask the stage rule chooses. The segment's own door and the

universal fallback (icp.default_door, the free consult) follow as secondary links. The office

lists them at /strategy/doors, from the same registries.

kindwhere it goessegments today
form/inquire/<form>?segment=…smb-local (workshop-seat) · municipal (capability-brief) · mission-nonprofit (mission-lane) · publishers, directories (inquiry)
book/book/<event>?segment=…broken-attribution (audit-30) · neutral-builder, capital-allocators (walkthrough-30) · enterprise-dtc, agency-resellers (discovery-45)
externalthe product (SIE)compliance-exposed · data-brokers · gtm-consolidators
default/book/consult-60everyone else

A form door may set detail: true to ask the second step right after the short ask. None does

today: the detail step is offered, never imposed.

The stage rule — ask by stage

forms.json stage_rule, applied by apps/web/lib/frontdoor/stage.ts and mirrored on the office page:

stagesignalfirst ask
warmutm_campaign or ref in the query, or a contact on file behind a signed known= tokenbook — the segment's own event, else the event scheduling.json names for it, else the consult
product-ledthe segment's door is externalthe product (SIE trial)
coldeverything elsethe short form — the segment's own form, else /inquire/inquiry step one

Warm wins over product-led: a campaign click gets the call. /go/compliance-exposed shows the SIE

trial first; /go/compliance-exposed?utm_campaign=x shows the consult first. The known token is

an HMAC over the contact id + a timestamp (30 days), minted by `lib/frontdoor/token.ts

mintKnownToken` for outbound links — a raw email is never a stage signal and never in a URL.

The two-step form

forms.json is derived from the saved Typeform definition (inquiry-form.typeform.json, 21

questions, 8 logic blocks, 15 jump actions) by engine/stages/derive_intake_forms.py. The

definition stays the spec; edit the derivation, never the output.

stepfieldswhen
startfirst name · email · company · the one need · consent (5)always, first, alone
detailthe other 17 Typeform questionswhen the door sets detail: true, or when the person taps "add the detail" after the door is shown

The three short forms (mission-lane · capability-brief · workshop-seat, 9 fields) are one step each.

Flow: POST /api/intake {step: "start"} validates only the start fields, writes the contact

(consent receipt at the root), the inquiry and a touch, routes by the need answer (or the

landing page's preset segment), and answers with the door, detail, and a prefill token —

an HMAC over the email on file + the inquiry id + a timestamp, 30 minutes. Step two calls

GET /api/intake/prefill?token= (returns only that inquiry's own asked answers; never another

person's; never the row's ip / user agent / receipt) and `POST /api/intake {step: "detail",

token}`; the answers merge into the same inquiry, the route is recomputed (an org type that

names a segment — municipal, agency, enterprise, SMB — overrides), the contact is updated by role,

a touch is recorded. dry: true validates and routes without writing. Every refusal the endpoint

had stays: honeypot, unknown form, required-when-shown, a choice the field lacks, a bad email,

consent missing, the per-address throttle.

The branch map — every Typeform jump accounted for

Each of the 15 actions is either a show_if twin on its target field or an entry in

unmapped_logic with a reason; verify_intake re-enumerates the definition and fails on any

action with neither, on a recorded twin that is not actually on the field, and on an else-jump that

skips a field with no show_if (Typeform would never ask it; the office form would).

ruleTypeformtwin / entry
org type is Municipality → procurementisshow_if on procurement
need is Marketing / AI & Automation / Software / Creative / Media-PR → the five branch questions5 × isshow_if on each branch question
approve-budget is_not ticked → decision-makeris_not (checkbox)show_if {checked: false} on decision-maker
org type → need · need → budget · approve → keeping-you-up · each branch question → budget8 × alwaysunmapped_logic: else-jump fallthrough; each records the fields it skips, all of which are conditional

7 twins + 8 unmapped = 15. derive_intake_forms.py prints the tally; the office page shows it.

The field map

When governance/intake/field_map.json exists (the contact-graph stream writes it), every

forms.json field id must name one destination — the gate fails otherwise and the office page shows

coverage per form. Absent, the gate prints a note and skips.

The public host — a proxy, not a copy

scripts/deploy.py deploy_lab writes the Lab's Build Output config.json with, after the

filesystem handle and before the SPA fallback, proxies for /go, /inquire, /book,

/api/intake, /api/book, /og-image.png and /sitemap-doors.xml to

https://temerarii-office-app.vercel.app. One implementation; the public host inherits it:

NEXT_PUBLIC_SITE_URL defaults to https://temerarii.xyz, so canonicals, share cards, the app

sitemap and robots.txt name the public host. The Lab's robots.txt (rendered by

render_static_blog.py) lists both sitemaps: its own and /sitemap-doors.xml. On the app the

doors' sitemap is /sitemap.xml (the door pages, the forms, the rail — nothing gated) and the proxy

maps the public name onto it (LAB_PROXY_ALIASES), because that path is already outside the

operator gate; the app also answers /sitemap-doors.xml itself once middleware.ts PUBLIC_PATHS

lists it.

GA4 — on the doors only

NEXT_PUBLIC_GA4_ID (default G-PZKRJH7Y1F, the temerarii.xyz property, in .env.example;

inlined at build time — a change needs a rebuild). lib/frontdoor/analytics.tsx loads the tag

only on /go, /inquire, /book and fires:

eventwhereparams
door_view/go/<segment>segment · stage · reason · primary kind
form_startfirst answer on a formform · step · segment
form_submita step accepted by /api/intakeform · step · segment
bookinga booking accepted by /api/book (observed on the response — the scheduler owns the request)event type · segment

No email, no name, no answer text rides on an event. The office never carries the tag.

What the Chairman switches

  1. After the probe of temerarii.xyz/go/<segment> (200, canonical on xyz, tag present):

Duda's "Get started" → https://temerarii.xyz/go. The Typeform stays the spec until then.

  1. detail: true on any form door in gtm.yaml where the second step should be asked outright.
  2. INTAKE_TOKEN_SECRET and NEXT_PUBLIC_GA4_ID on the app's deployment; NEXT_PUBLIC_SITE_URL

only if the public host ever changes.

Files

governance/intake/forms.json (steps · stage_rule · analytics · logic_twins · unmapped_logic) ·

engine/stages/derive_intake_forms.py · engine/gates/verify_intake.py ·

apps/web/lib/frontdoor/{data,intake,stage,token,analytics,store}.ts(x) ·

apps/web/app/api/intake/{route,prefill/route}.ts · apps/web/app/inquire/[form]/{page,StepForm}.tsx ·

apps/web/app/go/[segment]/{page,DoorView}.tsx · apps/web/app/sitemap-doors.xml/route.ts ·

scripts/deploy.py (LAB_PROXY_PREFIXES) · engine/stages/render_static_blog.py (robots) ·

apps/office/render_frontdoor.py → /strategy/doors.

The doors as landing pages

The Chairman's audit (2026-08-25, through the public host): /go answered in 7.5 s, a door in

1.8 s, the form in 3.0 s, the rail in 4.4 s — every page a dynamic route reading three registries

from disk on every request, cold-started — and the copy on them was the internal GTM registry

(who · pain · trigger · message · wedge) shown verbatim. They read as made for the team, because

they were. This section is what replaced them.

Copy as data. governance/intake/landing.json holds, per segment, a block written for the

buyer the segment's titles name: hero (eyebrow · headline ≤ 12 words · sub ≤ 30) → stakes

(their pain and trigger, three lines, in their words) → mechanism (what we do, three steps,

from the playbook) → proof (≤ 3 receipts the reader can open) → offer (the ask per stage) →

objections (≤ 3) → close. A shared block carries the machine in one paragraph, the consent

promise and the no-payment line; _rules states the rails the copy follows and the word limits.

gtm.yaml keeps the internal fields for the office and the machine; none is shown to a buyer any

more. The CTA labels stay the doors' own (gtm.yaml door.label), so the office page at

/strategy/doors and the app cannot disagree on what a button says.

The funnel order on /go/<segment>: hero (eyebrow · the one h1 · sub · the primary ask by

stage · "see how it works") → laser rule → stakes → mechanism → proof → the offer (the one ask,

the other doors as quiet links) → objections (native <details>, no script) → close (h2 + the

same ask) → footer (consent promise · no payment · all doors · the office · the Lab). /go is

the hero plus the thirteen doors as cards grouped by pillar, each in the buyer's words. The form

and the rail wear the same shell (trim · header · grid · footer); StepForm and the Scheduler are

unchanged and sit on a white ground — a sanctioned brand ground — so their light styling holds.

The brand rules applied. Black ground (--tm-bg), white type (--tm-fg-1/2/3), red as

TRIM only: the eyebrow (red-400 for AA at 13 px), the hairline at the top of every page, the

laser-line dividers, the primary CTA (red-500 fill with the ember glow), the bracketed red

wireframe on proof cards, the UI accents. Never red body text on black. Tahoma Bold for every

heading, Tahoma Regular for body, JetBrains Mono for eyebrows and counters — all through the

generated tokens (styles/tokens.generated.css), no hex literal in a page. The effects are the

brand book's, in CSS only: the 44 px working grid showing through, the glow, the wireframe, the

laser line, and a slow dot drift under the hero ("signal field", ember-drift timing) that is a

static frame under prefers-reduced-motion. No 3D, no R3F, no web-font import: load time is the

first conversion factor. Mobile-first; the primary CTA sits above the fold at 390 × 844.

The static-render decision. /go, /go/[segment], /inquire/[form] and /book/[event]

are force-static with generateStaticParams (13 doors · 4 forms · 4 events), built once from

the registries; lib/frontdoor/data.ts reads each registry once per process (readRegistryOnce)

for the API routes that stay dynamic. The stage rule moved to the browser

(components/frontdoor/StageAsk + lib/frontdoor/stage-client.ts): the server renders the cold

ask, the client reads utm_campaign · ref · known after hydration and swaps in the warm ask

with the space reserved, then fires door_view once with the stage it resolved. The one honest

delta: a known token cannot be HMAC-verified in the browser, so one with the minted shape

inside its 30-day life counts as warm; a forged one only changes which of two open doors is the

button, and every API route still verifies for real. ?segment= on the form and the rail is

read the same way (useQuerySegment) and handed to StepForm / Scheduler exactly as before. The

booking page states the hosts' hours DERIVED from scheduling.json once confirmed: true; the

default short-form label counts the start step's questions from forms.json rather than saying

"five" (the step is seven fields now, six questions and the consent).

Before / after (before = the audit through the public host; after = next start locally,

warm, so the host adds its own network time but no render):

routebefore TTFBafter TTFBafter HTMLscripts (src)h1sections
/go7.5 s · ƒ dynamic9 ms · ○ static25.2 KB11 (6)1hero · doors · close
/go/compliance-exposed1.8 s · ƒ10 ms · ● SSG27.9 KB13 (7)1hero · stakes · mechanism · proof · offer · objections · close
/go/municipal?utm_campaign=x—13 ms · ● (cache HIT)27.1 KB12 (7)1the seven
/inquire/inquiry3.0 s · ƒ14 ms · ● SSG28.3 KB12 (7)1hero · form
/book/consult-604.4 s · ƒ18 ms · ● SSG16.4 KB12 (7)1hero · booking

The script count did not fall (11–14 before): six or seven are Next's chunks and the rest are the

inline RSC payload the app router ships on every page — that is the framework's floor, and the

payload is inline, not a request. Every page carries its canonical on temerarii.xyz and the GA4

id from the deployment env; POST /api/intake with dry: true still answers 200 with the door.

The gate. verify_intake now refuses a segment with no landing block, a missing section, a

field over its limit (headline ≤ 12 and sub ≤ 30 whatever the file says), a banned word from

brand.json in any of the file's strings, an offer whose door kind the stage rule would not

choose for that stage and segment (product must be null unless the door is external), and a

proof receipt that is not an office path existing under apps/office/public, an https URL on a

brand domain, or none; when a build is on disk it also refuses a built door page with other than

one <h1>.

Outside this page's reach. apps/office/render_frontdoor.py still labels the default short

form "Tell us in five questions" (the app counts; the office should read steps[0] the same

way). /inquire (the index) is a dynamic redirect. The Duda "Get started" link and the host's

DNS are the Chairman's switches, unchanged.