Landing Page System / Under the hood
One repo, one static host, a thin lead relay, and a handful of locked layers. Builders shape layout and copy; everything below this line is shared, owner-managed, and identical on every page.
Nobody rebuilds these. Every page gets all of them automatically by starting from the template.
Pinned tokens in every page's :root, straight from the Vault's brand system. Self-hosted Fraunces and DM Sans variable woff2 (fonts.css) with the Fraunces axis lock (opsz 144 · SOFT 100 · WONK 0), self-hosted Phosphor Light icons (phosphor-light.css). Backgrounds are Snow, never pure white; one Coral CTA per page; raw hex outside the palette fails CI.
kh-gate.js (blocking, first script in every head) plus the registry at assets/pages.json. The script hides the page, fetches the registry with a cache-busting query (so a status flip beats the long /assets/* max-age and Cloudflare), and shows a team-PIN overlay for anything not approved, on every host including production. The PIN checks against a SHA-256 hash; a sessionStorage fast path skips the fetch once a page is confirmed approved. Archived pages show a retired notice instead. Early-visibility control, not a security boundary.
lead-capture.js powers the template's locked form block. It captures first-touch attribution on arrival (UTMs, gclid/fbclid, landing page, referrer) into sessionStorage so it survives navigation before submit, stamps it into hidden fields, generates a lead_id (UUID) per submit, and POSTs to the relay. The honeypot field contact_reference drops bots client- and server-side, and the hidden source_site / source_page fields mark every lead as an LP lead in Salesforce.
POST /api/lead (server.js, Sevalla app) origin check, honeypot drop, enrichment; the browser never sees the webhookThe thank-you page books the appointment. The LP captures; Apptoto schedules; nobody rebuilds the scheduler.
After submit, the form stashes the lead in sessionStorage as khp_lead and redirects to the page's thank-you/. That page reads the lead, maps ZIP to region via zip-regions.js, and embeds that region's Apptoto booking page with name and contact prefilled, so the prospect only picks a date and time. The lead_id rides along as the join key back to the Salesforce lead. Each page sets its campaign's APPOINTMENT_TYPE deep-link slug; the verified slugs:
exteriorpaintinterior_painting_appointmentcabinet_painting_estimateAn invalid appointment-type slug does not error. Apptoto silently books the first appointment type (exterior), so a typo sends interior leads to exterior estimates. Never guess a slug; verify it on the region's public booking page first.
Runs on every PR and every push to main. Green lint is the merge condition; there is no human code review on page PRs.
noindex meta, kh-gate.js blocking in the head, the GA4 pair, favicon, fonts.css, phosphor-light.css, and the deferred lead scripts.data-thank-you target must exist and carry the Apptoto embed, the khp_lead handoff, and zip-regions.js; it warns while the fallback still points at the test scheduler.approved or archived fails unless the PR author is storystak (Zach) or coreymorgan (Corey). Approval is a real gate, not a convention.One GitHub repo, one folder per page. The static site serves main; server.js runs separately as the lead relay.
kind-home-landing-pages/ ├── _template/ page skeleton every build starts from (+ thank-you/) ├── assets/ LOCKED: kh-gate.js, lead-capture.js, address-autofill.js, zip-regions.js, fonts, icons, logos │ └── pages.json the registry: the go-live switch ├── your-page/ one folder per page, with img/ and thank-you/ ├── exterior/ interior/ cabinet/ the live service pages ├── status/ team dashboard: pages, leads, UTM builder (PIN) ├── scripts/ lint-pages.mjs, the CI check ├── server.js the /api/lead relay (Sevalla app) └── .github/ the "Page lint" workflow
Back to the process: the step-by-step path from idea to live page.