GAP School Module 08 — Conversion Tools Lesson 8.3

A visitor who lands on a listing page, doesn’t fill out the contact form, and navigates away is not a lost lead — they’re a lead you haven’t captured yet. The site-wide modal is a second chance at capture. It shows up when behavior signals suggest the visitor is about to leave: mouse leaving the viewport, 70% scroll depth without form interaction, or 45 seconds elapsed without engagement.


The situation

The Anchor site’s main contact form was on individual listing pages. A buyer browsing the inventory grid — not yet committed to a specific unit — had no prompted lead capture path. Visitors who reached 70%+ scroll depth on the homepage or browse page were interested enough to scroll; they just hadn’t been asked for their contact information.


What I did

Single modal registered once via wp_footer

The modal HTML is injected globally across every page via wp_footer. No per-page modal, no duplication. Suppressed on the contact page and for visitors who have already submitted a lead:

PHP
add_action( 'wp_footer', '[client]_render_global_modal' ); function [client]_render_global_modal(): void { if ( is_page( [ 'contact', 'credit-application' ] ) ) { return; } if ( isset( $_COOKIE['[client]_lead_submitted'] ) ) { return; } ?> <div id="[client]-modal" class="site-modal" role="dialog" aria-modal="true" aria-hidden="true"> <div class="site-modal__overlay" id="[client]-modal-overlay"></div> <div class="site-modal__box"> <button class="site-modal__close" id="[client]-modal-close" aria-label="Close">&times;</button> <div class="site-modal__inner"> <?php [client]_render_modal_lead_form(); ?> </div> </div> </div> <?php }

Three trigger conditions

The modal fires on the first of three conditions. Once triggered, a 7-day cookie prevents re-triggering:

JavaScript
(function() { var MODAL_COOKIE = '[client]_modal_dismissed'; var COOKIE_DAYS = 7; function getCookie(name) { var match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); return match ? decodeURIComponent(match[2]) : null; } function setCookie(name, value, days) { var exp = new Date(Date.now() + days * 864e5).toUTCString(); document.cookie = name + '=' + value + '; expires=' + exp + '; path=/; SameSite=Lax'; } var modal = document.getElementById('[client]-modal'); var overlay = document.getElementById('[client]-modal-overlay'); var closeBtn = document.getElementById('[client]-modal-close'); if (!modal || getCookie(MODAL_COOKIE)) return; var triggered = false; function trigger() { if (triggered) return; triggered = true; modal.setAttribute('aria-hidden', 'false'); modal.classList.add('is-open'); setCookie(MODAL_COOKIE, '1', COOKIE_DAYS); } function dismiss() { modal.setAttribute('aria-hidden', 'true'); modal.classList.remove('is-open'); } // Trigger 1: Exit intent (cursor leaving viewport upward) document.addEventListener('mouseleave', function(e) { if (e.clientY <= 0) trigger(); }); // Trigger 2: Scroll depth (70%) window.addEventListener('scroll', function() { var scrolled = window.scrollY + window.innerHeight; var docHeight = document.documentElement.scrollHeight; if (scrolled / docHeight >= 0.70) trigger(); }, { passive: true }); // Trigger 3: Time delay (45 seconds) setTimeout(trigger, 45000); if (closeBtn) closeBtn.addEventListener('click', dismiss); if (overlay) overlay.addEventListener('click', dismiss); document.addEventListener('keydown', function(e) { if (e.key === 'Escape') dismiss(); }); })();

Modal lead form → CRM routing

The modal form submits to the same AJAX handler as the main inquiry forms. No duplicate CRM creation code:

PHP
add_action( 'wp_ajax_nopriv_[client]_modal_lead', '[client]_handle_modal_lead' ); add_action( 'wp_ajax_[client]_modal_lead', '[client]_handle_modal_lead' ); function [client]_handle_modal_lead(): void { check_ajax_referer( '[client]_modal_lead', 'modal_nonce' ); $lead_id = [client]_create_lead( [ 'first_name' => sanitize_text_field( $_POST['first_name'] ?? '' ), 'phone' => sanitize_text_field( $_POST['phone'] ?? '' ), 'source' => 'site_modal', 'status' => 'new', ] ); [client]_enroll_in_sequence( $lead_id, 'modal_capture' ); // Suppress modal for this visitor for 90 days after submission setcookie( '[client]_lead_submitted', '1', time() + ( 90 * DAY_IN_SECONDS ), '/' ); wp_send_json_success( [ 'message' => 'Got it. We\'ll be in touch.' ] ); }

Why it matters

The modal form converts differently than the main inquiry form for a structural reason: the main inquiry form asks “are you interested in this specific unit?” The modal asks “can we stay in touch?” The ask is smaller. The commitment is lower. The conversion rate is higher.

The source attribution on modal leads is important. Modal leads have different conversion rates than listing-page leads. Without source tracking, you can’t measure the modal’s actual impact on qualified pipeline.


The Anchor build

Modal conversion rate: 4.2% of non-suppressed page views. Exit intent was the most common trigger (58%), scroll depth second (31%), time delay last (11%). Modal leads converted to appointment at 1.4× the rate of cold-browse leads — the intent signal from scroll depth filtering was meaningful.


Do this, not that

  • Suppress the modal for visitors who have already submitted a form. Showing a “tell us what you’re looking for” modal to someone who just filled out a contact form is friction, not conversion.
  • Use cookie-based suppression, not session-based. Session suppression fires the modal again on every new browser session. A 7-day cookie is what most users expect from dismissing a modal.
  • Submit to the same AJAX handler as your main forms. No duplicate CRM creation code, no duplicate sequence enrollment. The modal is a different entry point to the same pipeline.
  • Track source attribution on modal leads. site_modal as a source lets you measure this conversion layer independently from listing-page and direct inquiries.
  • Don’t fire on the first page load with no interaction. The 45-second timer and scroll depth requirements ensure there’s an intent signal before the modal appears.
When you’re ready to build

The lessons are yours. When you want it built, we’re here.

Every lesson stays free — no account, no paywall, no email gate, ever. But if you’d rather have this system standing on your business than wire all 48 lessons yourself, leave your email. We’ll send you a direct line to a build — and you’ll be first to hear when we add new tools to the curriculum.

None of this gates a single lesson. The curriculum was free before you got here and it stays that way.

We’ll use your email to send you a fast-track to a GAP build and occasional notes on how GAP builds digital sales departments. Lessons stay 100% free — no email required to read any of them. We never share or sell your information. Unsubscribe any time. Privacy policy at gapindustriesllc.com/privacy.html.

Done learning how it’s built? We’ll build it.

You came here to understand the system, and now you do. If you’d rather have it standing on your business than spend the next three months wiring it yourself, GAP Concierge is the same architecture from these lessons — a white-label AI agent that knows your catalog and captures your leads — set up for you, from $97/mo.

See GAP Concierge →