Marketing Landing Page System

Landing Page System  /  Under the hood

What's 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.

1

The three locked layers

Nobody rebuilds these. Every page gets all of them automatically by starting from the template.

The brand layer Locked

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.

The review gate Locked

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.

The lead layer Locked

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.

Locked form block attribution + lead_id stamped into the payload
Relay: POST /api/lead (server.js, Sevalla app) origin check, honeypot drop, enrichment; the browser never sees the webhook
Zapier maps the payload, holds Testing123 out of the pipeline
Salesforce full attribution + lead_id, the join key to the Apptoto booking
2

The Apptoto handoff

The thank-you page books the appointment. The LP captures; Apptoto schedules; nobody rebuilds the scheduler.

ZIP in, booked appointment out

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:

Exteriorexteriorpaint
Interiorinterior_painting_appointment
Cabinetcabinet_painting_estimate

The invalid-slug trap

An 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.

3

The CI: "Page lint"

Runs on every PR and every push to main. Green lint is the merge condition; there is no human code review on page PRs.

What it enforces

  • Locked includes present on every page: noindex meta, kh-gate.js blocking in the head, the GA4 pair, favicon, fonts.css, phosphor-light.css, and the deferred lead scripts.
  • Hex allowlist: any raw hex outside the pinned Kind Home palette fails the build. Tokens only.
  • Registry entry for every page folder, and an image cap: any page image over 400 KB fails.
  • Thank-you rules: every form's 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.
  • Locked-path isolation: a PR that mixes page work with shared locked files fails. Shared-layer changes ship alone, code-owner reviewed.
  • Approver guard: a registry flip to approved or archived fails unless the PR author is storystak (Zach) or coreymorgan (Corey). Approval is a real gate, not a convention.
4

The shape of the repo

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

How to use this system

Back to the process: the step-by-step path from idea to live page.