Wesley Financial Group Marketing Landing Page System

Landing Page System  /  Under the hood

What's under the hood.

One repo, one static host, a thin lead API, and five locked layers. Builders shape layout and copy; everything below this line is shared, owner-managed, and identical on every page.

1

The shape of it

A single GitHub repo deploys two things on Sevalla: the static site and the lead API.

Static site

A Sevalla static site serves lp.wesleyfinancialgroup.com straight from main. No build step: what is in the repo is what is on the host. PR previews are on, so every open PR gets its own URL. One folder per page, always index.html, lowercase-hyphenated slugs.

Lead API

A small Sevalla application (wfg-lp-api, Express) built from the repo's server/ folder. It exposes POST /api/lead for the form components and a /health check. Secrets (the Zapier webhook, the Turnstile secret) live in Sevalla environment variables, never in code.

wfg-landing-pages/
├── _template/          page skeleton every build starts from (+ thank-you/)
├── assets/             LOCKED: lp-tokens.css, lp.js, lp-gate.js, fonts, logos
├── your-page/          one folder per page, with img/ and thank-you/
├── info-kit/           shared conversational form page (Pattern A)
├── status/             team dashboard: every page, owner, status (PIN)
├── pages.json          the registry that drives the review gate
├── server/             the lead API (Sevalla application)
├── scripts/            lint-pages.mjs, the CI check
└── .github/            CI workflow + CODEOWNERS on shared paths
2

The locked layers

Five shared pieces that nobody rebuilds. Every page gets all of them automatically by starting from the template.

Brand tokens Locked

/assets/css/lp-tokens.css, pinned from the WFG Vault. Self-hosted Plantin MT Pro (headlines) and Nunito Sans (body), the full color palette, type scale, spacing, buttons, and cards. Pages style only with var(--wfg-...); raw hex fails CI. Green is strictly conversion: CTAs, links, active states.

Function layer Locked

  • First-touch + last-touch UTM and click-ID capture: 90-day cookies on .wesleyfinancialgroup.com, atomic per ad touch
  • Visitor UUID and CTA link decoration to WFG subdomains
  • GTM dataLayer events: lp_view, lp_cta_click, lp_form_start, lp_form_step, lp_form_submit, lp_form_error, lp_thank_you
  • Defines the <wfg-lead-form> and <wfg-convo-form> components

Review gate Locked

/assets/js/lp-gate.js plus pages.json. A blocking script in the head hides the page, reads the registry, and shows a team-PIN overlay for anything not approved, on every host including production. The PIN is checked as a SHA-256 hash against the registry. Archived pages show a retired notice instead. It is an early-visibility control, not a security boundary.

Lead API Locked

Validates required fields, drops honeypot submissions silently, verifies the Turnstile token server-side, then forwards the payload to the Zapier Catch Hook that creates the Salesforce lead. If Cloudflare itself is down it fails open: a paid lead beats a blocked bot. If Zapier fails, the browser gets an error and can retry, and Zapier task history is the replay backstop.

CI Locked

The Page lint check runs on every PR and push to main: locked includes present on every page (GTM, noindex, favicon, tokens, gate, lp.js), tokens-only styling (raw hex rejected), a pages.json entry for every page folder, page images under 400 KB, and no mixing of page work with locked shared paths in one PR. It also enforces that only approvers (Zach, David, Cameron) can flip a page to approved or archived. CODEOWNERS requires owner review on all shared paths.

3

How a lead gets captured

Two patterns, chosen per page. Both deliver an identical payload into Salesforce.

Pattern A

CTA to the conversational form

The page's CTA links to /info-kit/, the shared Typeform-style page: one question per screen, powered by <wfg-convo-form>. Links to external WFG form URLs work too; lp.js decorates them with attribution automatically.

Landing page CTA attribution rides first-party cookies
/info-kit/ conversational form one question per screen
Shared lead API validate, verify, forward
Pattern B

Inline form on the page

The page drops in <wfg-lead-form>: one name field, phone masked as the user types, email, and an optional single address field with Google Places autofill. Turnstile and the TCPA disclaimer are built in. Builders place it and style it; they never rewire it.

<wfg-lead-form> component attribution stamped into the payload
Turnstile + honeypot required, the endpoint is public
Shared lead API validate, verify, forward

One identical payload, into Salesforce

Both paths deliver the same schema through the shared Zapier Zap. Field names deliberately mirror the main site's HandL fields (first_utm_*, handl_*, traffic_source, gaclientid, _fbc/_fbp), so a landing page lead maps to the same Salesforce fields as a website lead, and every lead carries its source page.

The Turnstile split

Spam protection is Cloudflare Turnstile, split across two places so the secret never touches page code.

On the page Site key · public

The Turnstile widget mounts when the visitor first focuses the form and hands it a one-time token. The token ships in the payload alongside the attribution fields.

In the lead API Secret key · private

The server verifies the token with Cloudflare before forwarding to Zapier, and rejects anything that fails. The secret lives in a Sevalla environment variable.

4

Guardrails & conventions

The full rulebook behind the short list on the overview page. CI enforces most of it on every PR.

The builder flow in full

  • Stage the approved HTML and every image in a new subfolder of the team Drive folder Marketing - Claude > WFG > shared > web > Landing Pages, named after the page slug. Prefix image files with the slug (e.g. meta-vsl-hero.jpg) so the Drive connector can find them.
  • Branch from main, named <you>/<page-slug> (fixes: <you>/<page-slug>-fix). One page per branch; never commit to main directly.
  • cp -r _template <page-slug> and move the approved content in, keeping every locked include. One folder per page, always index.html, lowercase-hyphenated slugs.
  • Images go to <page-slug>/img/, converted to webp with srcset variants for large photos.
  • Register the page in pages.json (in-progress, then in-review), keep the template's thank-you/ subfolder and point the form's data-redirect at it, verify at a 390px viewport, then PR into main.

Styling & icons

Every color, font, radius, and shadow comes from var(--wfg-...); raw hex fails CI. Green is conversion only: CTAs, links, active states — never a background or decoration. Icons are Phosphor Regular, monotone, in Navy, Gold, or White. No emoji, ever.

The locked includes

Carried by the template and required by CI on every page: the GTM snippet (head + noscript), the noindex meta, the favicon links, lp-tokens.css, lp-gate.js (blocking, in the head), and lp.js (defer). Never strip any of them.

Form field rules

One name field (full_name), never split into first/last. Phone masks to (xxx)xxx-xxxx as the user types. Address is one field with Places autofill, never split into street/city/state/zip. The components enforce these; never work around them, never build a custom form or call the lead API directly.

Copy compliance

Canonical Vault stats only, never invented numbers. "Resolution Specialists," never "lawyers." "Near-Perfect Success Rate," never "100%" or "guaranteed." Never advise owners to stop payments. 7th-grade read level.

Image budget

Aim under ~200 KB per image; CI hard-fails anything over 400 KB. Most WFG traffic is mobile, so heavy images are a conversion tax. Brand marks are never re-uploaded: logos and the lion come from /assets/logos/, approved brand imagery from the WFG Vault.

5

True on every page

Baked into the locked layers, so no builder has to think about them.

Attribution that follows

First and last touch captured on arrival, 90 days, root-domain cookies, atomic per ad touch so networks never bleed into each other.

Spam gate

Honeypot plus Turnstile, verified server-side in the lead API, because the endpoint is public.

noindex

Paid pages stay out of organic search so they never compete with the main site.

One analytics spine

Standardized lp_* events into GTM container GTM-N4NN3KX, so reporting works across every page the team ships.

No dropped leads

Zapier task history is the backstop, with retry on failure. A paid lead is real money.

Know the source

Every lead carries its own page slug and path, so you can compare page against page.

How to use this system

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