Plugin
MFX Landing Pages
Campaign landing pages with native RVR booking forms — for Hudson Valley Luxury Resorts.
Features
- Custom post type with top-level URL routing — slugs live at /world-cup-2026/, no /landing-page/ prefix
- Native RVR booking submission — no proxy, no middleware, exactly one code path that creates a booking
- Real-time pricing calculator that replicates RVR's calculateCost() exactly (seasonal, bulk, fees, tax)
- iOS-safe modal booking UX with per-property pre-selection from any link or estate card
- Auto-generated /sms-terms/ page satisfying TCPA + CTIA Monitoring Handbook disclosures
- Per-booking attribution — every submission tagged with _mfx_booking_source = landing page slug
- CodeMirror-powered HTML / CSS / JS editor per landing page, plus per-page Open Graph image
- Static-HTML migration utility — converts existing campaign pages into CPT posts on activation
Hudson Valley Luxury Resorts ships campaign landing pages — World Cup 2026, Closer / Converter funnels — that need a real booking form, not a contact form. Until this plugin existed, those pages were standalone HTML files deployed over SCP, each with its own hand-rolled booking form posting to a custom WordPress endpoint, each with its own copy of the pricing JS, and none of them tagging the resulting bookings with which page they came from.
MFX Landing Pages replaces all of that. The marketing team edits a landing page like any other post, drops [mfx_booking_form] where the form should appear, picks which properties belong to that campaign, and ships. The booking submission is the real RealHomes Vacation Rentals booking flow — same nonce, same handler, same booking CPT — with two extra meta entries on the resulting booking: which landing page sent it, and whether the guest opted into SMS.
How it submits
The submit button posts to admin-ajax.php?action=rvr_booking_request — the same endpoint the RealHomes Vacation Rentals theme widget calls from a property’s own page. RVR’s handler runs the nonce check, the email check, reCAPTCHA verification, the min-stay gate (per-property AND seasonal), the capacity gate, then inserts a new booking CPT.
Because we ride RVR’s own handler, every guard rail RVR ships with applies automatically. The plugin doesn’t validate anything server-side; the only thing it adds is two add_post_meta() calls hooked on wp_insert_post — _mfx_sms_consent and _mfx_booking_source — which fire inside the RVR handler’s own insert and capture the two fields the handler doesn’t know about.
There’s no proxy, no shadow handler, no parallel validation. There is exactly one code path that creates a booking on this site, and that code path is RVR’s.
The pricing calculator
Real-time price preview runs in a vanilla-JS module that replicates the calculation closure inside rvr-booking-public.js exactly: per-day seasonal lookup, bulk-night discounts (largest threshold met wins), extra-guest charges, service charges across all four calculation modes (per_stay / per_guest / per_night / per_night_guest), government tax across the same four modes, configurable additional fees, and optional amenities.
The PHP side normalizes a property’s RVR meta into a single JSON payload — every field the calculator needs, in a clean shape — and localizes it onto the page with the form. The calculator reads that payload, not the raw RVR meta, so future changes to RVR’s storage layout don’t ripple into the JS.
Pricing trust matches RVR: the AJAX handler stores the client-supplied totals without recomputing them server-side. That’s safe because the calculator and the handler read the same source data — there is no version of the data the user can poison without also having edit access to the property post.
The booking modal
The form lives in markup at <section id="book"> so a JS-disabled visitor (or a crawler) sees the form inline and existing href="#book" anchors fall back to a plain page scroll. When JS initializes, the form is lifted into a fixed-position modal overlay and body.mfx-bf-modal-ready is set, which CSS uses to hide the inline section.
Every a[href="#book"] on the page becomes a modal trigger. On landing pages with an “Available estates” card grid, each card gets two buttons injected — Check Availability and Book Now — both carrying a data-mfx-bf-target-property attribute. Click any of them and the modal opens with that property already selected, the date picker primed, and the price summary panel showing the property’s image, location, and stats. The whole card is also click-to-open.
The overlay uses an iOS-safe scroll lock, Escape-to-dismiss, backdrop click-to-dismiss, and respects prefers-reduced-motion. Successful submissions auto-close the modal three seconds later via a MutationObserver on the success element — the AJAX submission code doesn’t need to know the modal exists.
SMS compliance
The booking form has an SMS opt-in checkbox. That checkbox is paired with a full /sms-terms/ page that the plugin creates on activation. The content satisfies TCPA and CTIA Monitoring Handbook disclosure requirements — sender identity, message frequency, opt-out (STOP), help (HELP), supported carriers, and a link to the site’s Privacy Policy. The page is a standard WordPress page (not a mfx_landing_page), so it remains editable in the block editor.
The opt-in itself is captured as _mfx_sms_consent meta on the resulting booking. Carrier aggregators that approve campaign SMS sending — Text Magic and similar — require a public, linkable terms page; without one, the consent record isn’t enforceable and the campaign can be rejected at approval.
Migration
The plugin ships a one-shot migration utility that converts the existing static HTML landing pages into CPT posts on activation. For each entry in its config, it reads the file, extracts the <body> inner HTML, hoists <style> blocks and the Google Fonts <link> from <head> into the body content, strips the Atlas tracker script (re-injected site-wide via mfx-resource-hints), removes the dead per-property “Check Availability” buttons, and replaces the inner content of <section id="book"> with the [mfx_booking_form] shortcode — preserving the section element itself so existing in-page anchors still resolve.
It’s idempotent: slugs that already exist as CPT posts are skipped. The migration runs on activation and is also invocable from WP-CLI. After it runs, the original static directories need to be renamed so Apache stops serving them directly — WordPress rewrite rules cannot win against a real index.html at the same path — but the migration deliberately doesn’t move the source files itself, leaving them as a backup until the CPT version is verified.
Where it sits in the suite
This is the only plugin in the MFX suite that doesn’t talk to the MoonFactory portal — no health endpoint, no heartbeat, no remote configuration. It’s a standalone site plugin, deployed only to the Hudson Valley Luxury Resorts WordPress install, and its dependency chain (RealHomes theme + RealHomes Vacation Rentals plugin + Easy Real Estate) is HVLR-specific too.
Everything in the plugin is namespaced mfx_lp_ — every function, class, hook, option, and meta key — to avoid collisions with the rest of the MFX suite that runs on the same site (mfx-sentinel, mfx-resource-hints, hvlr-seo).
Installation
Deployed and kept up-to-date automatically on the Hudson Valley Luxury Resorts WordPress site by the MoonFactory portal. There is nothing to install by hand, and no other client site receives this plugin.