PageSourceSearch

https://www.iwebdatascraping.com/js/jquery-form-validation.js

js iwebdatascraping.com collected 2026-10-08 03:29:15 UTC 31,297 bytes, 909 lines download raw bytes

1$(document).ready(function () {
2    grecaptcha.ready(function () {
3        grecaptcha
4            .execute("6LexOQssAAAAAAI80tgLm8ht96LIh7LKrlglSDJD", {
5                action: "submit_form",
6            })
7            .then(function (token) {
8                document.getElementById("recaptchaToken").value = token;
9            });
10    });
11
12    $('[name="contactPhone"]')
13        .off("keypress paste")
14        .on("keypress", function (e) {
15
16            const char = String.fromCharCode(e.which);
17
18            // Digits ONLY
19            if (!/\d/.test(char)) {
20                e.preventDefault();
21                return;
22            }
23
24            // Maximum 15 digits
25            const digits = this.value.replace(/\D/g, "");
26
27            if (digits.length >= 15) {
28                e.preventDefault();
29                return;
30            }
31
32            // Repeated numbers are allowed
33            // Example: 222222222222222 ✅
34        })
35
36        .on("paste", function (e) {
37
38            e.preventDefault();
39
40            const input = this;
41
42            const pasted = (e.originalEvent || e)
43                .clipboardData
44                .getData("text");
45
46            // Keep digits ONLY
47            const pastedDigits = pasted.replace(/\D/g, "");
48
49            const currentDigits = input.value.replace(/\D/g, "");
50
51            // Maximum 15 digits
52            const remaining = 15 - currentDigits.length;
53
54            const allowed = pastedDigits.slice(
55                0,
56                Math.max(0, remaining)
57            );
58
59            const start = input.selectionStart;
60            const end = input.selectionEnd;
61
62            input.setRangeText(
63                allowed,
64                start,
65                end,
66                "end"
67            );
68
69            $(input).trigger("input");
70        });
71
72    // ─────────────────────────────────────────────
73    //  GIBBERISH DETECTION ENGINE
74    // ─────────────────────────────────────────────
75
76    /**
77     * Returns a score 0–1 indicating how "English-like" a string is.
78     * Based on trigram frequency analysis of common English trigrams.
79     * Score close to 1 = real text, close to 0 = gibberish.
80     */
81    var COMMON_ENGLISH_TRIGRAMS = new Set([
82        "the",
83        "and",
84        "ing",
85        "ion",
86        "tio",
87        "ent",
88        "ati",
89        "for",
90        "her",
91        "ter",
92        "hat",
93        "thi",
94        "nth",
95        "int",
96        "ere",
97        "tho",
98        "ese",
99        "ste",
100        "est",
101        "his",
102        "was",
103        "you",
104        "tha",
105        "not",
106        "are",
107        "wit",
108        "all",
109        "can",
110        "our",
111        "one",
112        "had",
113        "ver",
114        "but",
115        "whi",
116        "has",
117        "hin",
118        "ith",
119        "have",
120        "eve",
121        "out",
122        "fro",
123        "pro",
124        "com",
125        "con",
126        "ect",
127        "men",
128        "per",
129        "ted",
130        "ive",
131        "res",
132        "rea",
133        "str",
134        "tra",
135        "ans",
136        "ons",
137        "use",
138        "ple",
139        "ble",
140        "sta",
141        "tin",
142        "ple",
143        "igh",
144        "age",
145        "rea",
146        "ual",
147        "tic",
148        "nes",
149        "ite",
150        "oth",
151        "ome",
152        "hou",
153        "nts",
154        "led",
155        "lin",
156        "ess",
157        "ame",
158        "oun",
159        "end",
160        "nce",
161        "ove",
162        "ser",
163        "ill",
164        "tur",
165        "als",
166        "eri",
167        "ore",
168        "les",
169        "ali",
170        "eld",
171        "ell",
172        "ven",
173        "ren",
174        "ati",
175        "ali",
176        "hor",
177        "ear",
178        "bec",
179        "wor",
180        "bet",
181        "pon",
182    ]);
183
184    function trigramScore(text) {
185        var letters = text.toLowerCase().replace(/[^a-z]/g, "");
186        if (letters.length < 3) return 1; // too short to judge, pass through
187        var total = 0;
188        var hits = 0;
189        for (var i = 0; i <= letters.length - 3; i++) {
190            var tri = letters.substring(i, i + 3);
191            total++;
192            if (COMMON_ENGLISH_TRIGRAMS.has(tri)) hits++;
193        }
194        return total === 0 ? 1 : hits / total;
195    }
196
197    /**
198     * Detects excessive consonant clusters (5+ consonants in a row).
199     * Real English rarely has more than 3 consonants together.
200     */
201    function hasExcessiveConsonantClusters(text) {
202        var letters = text.toLowerCase().replace(/[^a-z]/g, "");
203        return /[bcdfghjklmnpqrstvwxyz]{5,}/.test(letters);
204    }
205
206    /**
207     * Detects common keyboard-walk patterns like qwerty, asdf, zxcv, etc.
208     */
209    var KEYBOARD_PATTERNS = [
210        /qwert/i,
211        /werty/i,
212        /ertyu/i,
213        /rtyui/i,
214        /tyuio/i,
215        /yuiop/i,
216        /asdfg/i,
217        /sdfgh/i,
218        /dfghj/i,
219        /fghjk/i,
220        /ghjkl/i,
221        /zxcvb/i,
222        /xcvbn/i,
223        /cvbnm/i,
224        /qazwsx/i,
225        /wsxedc/i,
226        /edcrfv/i,
227        /12345/i,
228        /23456/i,
229        /34567/i,
230        /45678/i,
231        /56789/i,
232        /abcde/i,
233        /bcdef/i,
234        /cdefg/i,
235    ];
236
237    function hasKeyboardPattern(text) {
238        return KEYBOARD_PATTERNS.some(function (pattern) {
239            return pattern.test(text);
240        });
241    }
242
243    /**
244     * Detects repeated word-level gibberish like "aaa bbb ccc" or "haha haha haha".
245     */
246    function hasRepeatedWords(text) {
247        var words = text.trim().toLowerCase().split(/\s+/);
248        if (words.length < 3) return false;
249        var counts = {};
250        words.forEach(function (w) {
251            counts[w] = (counts[w] || 0) + 1;
252        });
253        var maxCount = Math.max.apply(null, Object.values(counts));
254        // If any single word makes up >60% of the message, it's suspicious
255        return maxCount / words.length > 0.6;
256    }
257
258    /**
259     * Checks for strings of random-looking characters with no vowels at all.
260     */
261    function hasNoVowels(word) {
262        return word.length >= 4 && !/[aeiou]/i.test(word);
263    }
264
265    /**
266     * Master gibberish check — combines all signals.
267     * Returns { isGibberish: bool, reason: string }
268     */
269    function detectGibberish(text) {
270        var trimmed = text.trim();
271        var words = trimmed.split(/\s+/);
272
273        // 1. Keyboard pattern check
274        if (hasKeyboardPattern(trimmed)) {
275            return { isGibberish: true, reason: "keyboard pattern" };
276        }
277
278        // 2. Excessive consonant clusters
279        if (hasExcessiveConsonantClusters(trimmed)) {
280            return { isGibberish: true, reason: "consonant cluster" };
281        }
282
283        // 3. Repeated word pattern
284        if (hasRepeatedWords(trimmed)) {
285            return { isGibberish: true, reason: "repeated words" };
286        }
287
288        // 4. Check each word individually for no-vowel gibberish
289        var noVowelCount = 0;
290        words.forEach(function (word) {
291            var clean = word.replace(/[^a-zA-Z]/g, "");
292            if (hasNoVowels(clean)) noVowelCount++;
293        });
294        if (words.length >= 2 && noVowelCount / words.length > 0.5) {
295            return { isGibberish: true, reason: "no vowels in words" };
296        }
297
298        // 5. Trigram frequency scoring (most powerful signal)
299        var score = trigramScore(trimmed);
300        // Threshold: below 8% trigram hit rate → likely gibberish
301        // Real English messages typically score 15–40%
302        if (score < 0.08 && trimmed.replace(/[^a-zA-Z]/g, "").length >= 8) {
303            return {
304                isGibberish: true,
305                reason: "low trigram score (" + (score * 100).toFixed(1) + "%)",
306            };
307        }
308
309        return { isGibberish: false, reason: "" };
310    }
311
312    // ─────────────────────────────────────────────
313    //  UTILITY FUNCTION
314    // ─────────────────────────────────────────────
315
316    function handleFormSubmit(form, token) {
317        const $submitButton = $(form).find('button[type="submit"]');
318        const formData = new FormData(form);
319        var resourcesDownload1 = $("#resourcesDownload").val();
320
321        if (token) formData.append("recaptchaResponse", token);
322        formData.append("resourcesDownload1", resourcesDownload1);
323
324        $.ajax({
325            url: "contact-form-submit-backend.php",
326            method: "POST",
327            data: formData,
328            processData: false,
329            contentType: false,
330            beforeSend: function () {
331                // $(".loader").fadeIn();
332                $submitButton.prop("disabled", true).text("Submitting...");
333            },
334            success: function (response) {
335                $(".loader").fadeOut();
336                try {
337                    response = JSON.parse(response);
338                    if (response.status === "success") {
339                        if ($("#web-form").hasClass("download-resources")) {
340                            var resourcesDownload = $("#resourcesDownload").val();
341                            var createElement = document.createElement("a");
342                            createElement.href = resourcesDownload;
343                            var filename = resourcesDownload.substring(
344                                resourcesDownload.lastIndexOf("/") + 1,
345                            );
346                            filename = filename.split("?")[0];
347                            createElement.download = filename;
348                            // createElement.click();
349                        }
350                        $(form).trigger("reset");
351                        Swal.fire({
352                            title: "Thank you for contacting us",
353                            html: "We will get back to you soon.<br><b>Closing in <span id='countdown'>5</span> seconds...</b>",
354                            icon: "success",
355                            timer: 5000,
356                            timerProgressBar: true,
357                            allowOutsideClick: false,
358                            didOpen: () => {
359                                const countdownEl =
360                                    Swal.getHtmlContainer().querySelector("#countdown");
361                                let timeLeft = 5;
362                                const timerInterval = setInterval(() => {
363                                    timeLeft--;
364                                    if (countdownEl) countdownEl.textContent = timeLeft;
365                                    if (timeLeft <= 0) clearInterval(timerInterval);
366                                }, 1000);
367                            },
368                        });
369                        setTimeout(() => (window.location.href = "thank-you.php"), 5000);
370                        hideFormLoader();
371                    } else {
372                        alert("Something went wrong. Please try again.");
373                        $submitButton.prop("disabled", false).text("Submit");
374                        hideFormLoader();
375                    }
376                } catch (error) {
377                    console.error("Invalid JSON response:", response);
378                    alert("Unexpected error. Please try again.");
379                    $submitButton.prop("disabled", false).text("Submit");
380                    hideFormLoader();
381                }
382            },
383            error: function (err) {
384                $(".loader").hide();
385                alert("Error submitting the form.");
386                console.error(err);
387                $submitButton.prop("disabled", false).text("Submit");
388                hideFormLoader();
389            },
390        });
391    }
392
393    // ─────────────────────────────────────────────
394    //  VALIDATORS
395    // ─────────────────────────────────────────────
396
397    $.validator.addMethod(
398        "noLeadingSpace",
399        function (value) {
400            return !/^\s/.test(value);
401        },
402        "Don't enter space.",
403    );
404
405
406    $.validator.addMethod(
407        "validPhoneNumber",
408        function (value) {
409            const cleaned = value.replace(/\D/g, "");
410            if (cleaned.length < 10 || cleaned.length > 15) return false;
411
412            const repeatedDigitPattern = /^(\d)\1{4,}$/;
413            const repeatedPattern = /(\d{2,})\1+/;
414            const sequentialPattern = /(\d{2,4})(?=\d)\1+/;
415
416            // const fakeNumbers = new Set([
417            //     "0000000000",
418            //     "1111111111",
419            //     "1234567890",
420            //     "1234567891",
421            //     "9999999999",
422            //     "0123456789",
423            //     "99999",
424            //     "+11234567890",
425            //     "+10000000000",
426            //     "+19999999999",
427            // ]);
428
429            // if (fakeNumbers.has(cleaned)) return false;
430            // if (
431            //     repeatedDigitPattern.test(cleaned) ||
432            //     repeatedPattern.test(cleaned) ||
433            //     sequentialPattern.test(cleaned)
434            // )
435            //     return false;
436
437            const reg = /^\+?[\d\s\-()]{10,20}$/;
438            return reg.test(value);
439        },
440        "Please enter a valid phone number.",
441    );
442
443    $.validator.addMethod(
444        "validEmail",
445        function (value) {
446            const reg = /^[a-zA-Z0-9._%+-]{2,}@[a-zA-Z0-9.-]{2,}\.[a-zA-Z]{2,}$/;
447            const blockedEmails = new Set([
448                "[email protected]",
449                "[email protected]",
450                "[email protected]",
451                "[email protected]",
452                "[email protected]",
453                "[email protected]",
454                "[email protected]",
455                "[email protected]",
456                "[email protected]",
457                "[email protected]",
458                "[email protected]",
459                "[email protected]",
460                "[email protected]",
461                "[email protected]",
462                "[email protected]",
463                "[email protected]",
464                "[email protected]",
465                "[email protected]",
466                "[email protected]",
467                "[email protected]",
468                "[email protected]",
469                "[email protected]",
470                "[email protected]",
471                "[email protected]",
472                "[email protected]",
473                "[email protected]",
474                "[email protected]",
475            ]);
476            const blockedDomains = new Set([
477                "@test.com",
478                "@example.com",
479                "@demo.com",
480                "@abc.com",
481                "@email.com",
482                "@theguestpostshop.com",
483                "@sharklasers.com",
484                "@guerrillamail.com",
485                "@guerrillamailblock.com",
486                "@guerrillamail.net",
487                "@guerrillamail.de",
488                "@spam4.me",
489                "@maildrop.cc",
490                "@10minutemail.com",
491                "@dispostable.com",
492                "@mailinator.com",
493                "@technodreamoutsourcing.com",
494                "@guestpostmate.com",
495                "@50pros.com",
496                "@web.de",
497                "@sbcglobal.net",
498            ]);
499
500            const emailLower = value.toLowerCase();
501            const parts = emailLower.split('@');
502            if (parts.length !== 2) return false;
503            const local = parts[0];
504            const dotCount = (local.match(/\./g) || []).length;
505            if (dotCount > 2) return false;
506            const domain = "@" + emailLower.split("@")[1];
507            return (
508                reg.test(value) &&
509                !blockedEmails.has(emailLower) &&
510                !blockedDomains.has(domain)
511            );
512        },
513        "Please enter a valid email address.",
514    );
515
516    $.validator.addMethod(
517        "meaningfulMessage",
518        function (value, element) {
519            const message = $.trim(value);
520
521            // Empty
522            if (!message) {
523                return false;
524            }
525
526            // Minimum 2 words
527            const words = message.split(/\s+/).filter(Boolean);
528
529            if (words.length < 2) {
530                return false;
531            }
532
533            // Must contain at least 2 letters
534            const letters = message.match(/[a-zA-Z]/g) || [];
535
536            if (letters.length < 2) {
537                return false;
538            }
539
540            // Reject obvious repeated characters
541            if (/(.)\1{4,}/i.test(message)) {
542                return false;
543            }
544
545            // Reject obvious keyboard spam
546            const lowerMessage = message.toLowerCase();
547
548            const spamPatterns = [
549                "qwerty",
550                "asdfgh",
551                "zxcvbn",
552                "qwert",
553                "asdf",
554                "zxcv"
555            ];
556
557            for (const pattern of spamPatterns) {
558                if (lowerMessage.includes(pattern)) {
559                    return false;
560                }
561            }
562
563            return true;
564        },
565        "Please enter a meaningful message."
566    );
567
568
569
570    $.validator.addMethod("minchar", function (value, element, param) {
571        return value.replace(/\s/g, "").length >= param;
572    }, "Please enter at least {0} characters.");
573
574    // ─────────────────────────────────────────────
575    //  INPUT SANITIZATIONS
576    // ─────────────────────────────────────────────
577
578    function sanitizeInput(input, removeDigits = false) {
579        let val = input.val();
580        val = val.replace(/^\s+/, "");
581        if (removeDigits) val = val.replace(/\d/g, "");
582        input.val(val);
583    }
584
585    $('[name="contactMessage"]').on("keypress", function (e) {
586        const char = String.fromCharCode(e.which);
587        if (this.value.length === 0 && char === " ") {
588            e.preventDefault();
589        }
590    });
591
592    // Fixed: was missing the $ selector prefix in original code
593    $('[name="contactMessage"]').on("paste", function (e) {
594        e.preventDefault();
595        const pasted = (e.originalEvent || e).clipboardData.getData("text");
596        const clean = pasted.replace(/^\s+/, "");
597        const input = this;
598        const start = input.selectionStart;
599        const end = input.selectionEnd;
600        input.setRangeText(clean, start, end, "end");
601    });
602
603    $('[name="contactName"]').on('keypress', function (e) {
604        const char = String.fromCharCode(e.which);
605        const value = this.value;
606
607        // Allow only letters, spaces and apostrophe
608        if (!/[a-zA-Z\s']/.test(char)) {
609            e.preventDefault();
610            return;
611        }
612
613        // Prevent space or apostrophe as first character
614        if (value.length === 0 && /[\s']/.test(char)) {
615            e.preventDefault();
616            return;
617        }
618
619        // Allow only ONE apostrophe
620        if (char === "'" && value.includes("'")) {
621            e.preventDefault();
622            return;
623        }
624
625        // Apostrophe must come after a letter
626        if (char === "'" && !/[a-zA-Z]$/.test(value)) {
627            e.preventDefault();
628            return;
629        }
630
631        // Prevent multiple consecutive spaces
632        if (char === ' ' && value.endsWith(' ')) {
633            e.preventDefault();
634            return;
635        }
636    });
637
638
639    $('[name="contactName"]').on('paste', function (e) {
640        e.preventDefault();
641
642        const pasted = (e.originalEvent || e)
643            .clipboardData
644            .getData('text');
645
646        // Allow only letters, spaces and apostrophe
647        let clean = pasted.replace(/[^a-zA-Z\s']/g, '');
648
649        // Remove leading space or apostrophe
650        clean = clean.replace(/^[\s']+/, '');
651
652        // Multiple spaces -> single space
653        clean = clean.replace(/\s+/g, ' ');
654
655        // Keep only ONE apostrophe
656        const apostropheIndex = clean.indexOf("'");
657
658        if (apostropheIndex !== -1) {
659            clean =
660                clean.substring(0, apostropheIndex + 1) +
661                clean.substring(apostropheIndex + 1).replace(/'/g, '');
662        }
663
664        // Apostrophe must come after a letter
665        clean = clean.replace(/'(?=$|[^a-zA-Z])/g, '');
666        clean = clean.replace(/([^a-zA-Z])'/g, '$1');
667
668        const input = this;
669        const start = input.selectionStart;
670        const end = input.selectionEnd;
671
672        input.setRangeText(clean, start, end, 'end');
673    });
674
675    function showFormLoader() {
676        if (!document.getElementById("form-loader")) {
677            const loader = document.createElement("div");
678            loader.id = "form-loader";
679            loader.innerHTML = `
680                <style>
681                #form-loader {
682                    position: fixed;
683                    inset: 0;
684                    background: rgba(255, 255, 255, 0.75);
685                    z-index: 99999;
686                    display: none;
687                    align-items: center;
688                    justify-content: center;
689                    flex-direction: column;
690                    gap: 10px;
691                }
692
693                .form-loader-spinner {
694                    width: 40px;
695                    height: 40px;
696                    border: 4px solid #ddd;
697                    border-top-color: #333;
698                    border-radius: 50%;
699                    animation: formLoaderSpin 0.8s linear infinite;
700                }
701
702                @keyframes formLoaderSpin {
703                    to {
704                        transform: rotate(360deg);
705                    }
706                }
707                </style>
708                <div class="form-loader-spinner"></div>
709                <span>Submitting...</span>
710            `;
711
712            document.body.appendChild(loader);
713        }
714
715        document.getElementById("form-loader").style.display = "flex";
716    }
717
718    function hideFormLoader() {
719        const loader = document.getElementById("form-loader");
720
721        if (loader) {
722            loader.style.display = "none";
723        }
724    }
725
726
727
728    // ─────────────────────────────────────────────
729    //  INITIALIZE FORMS
730    // ─────────────────────────────────────────────
731
732    const forms = [
733        { selector: "#footer-form", recaptcha: true },
734        { selector: "#web-form", recaptcha: true },
735        { selector: "#store-footer-form", recaptcha: true },
736    ];
737
738    forms.forEach((f) => {
739        const form = $(f.selector);
740        if (!form.length) return;
741
742        // Hidden fields: UTM, device, landing page
743        form
744            .find("input[name='website_source']")
745            .val(document.referrer || "Direct");
746        form.find("input[name='landing_page']").val(window.location.href);
747
748        const urlParams = new URLSearchParams(window.location.search);
749        let source = urlParams.get("utm_source");
750        let medium = urlParams.get("utm_medium");
751        let campaign = urlParams.get("utm_campaign");
752
753        if (!source && document.referrer) {
754            const refHost = new URL(document.referrer).hostname;
755            source = refHost;
756            if (/google|bing/.test(refHost)) medium = "organic";
757            else if (/facebook|instagram|linkedin|pinterest/.test(refHost))
758                medium = "social";
759            else medium = "referral";
760        } else if (!source) {
761            source = "direct";
762            medium = "none";
763        }
764
765        form.find("input[name='utm_source']").val(source);
766        form.find("input[name='utm_medium']").val(medium);
767        form.find("input[name='utm_campaign']").val(campaign);
768        form.find("input[name='campaign']").val(campaign);
769        form
770            .find("input[name='keyword_trigger']")
771            .val(urlParams.get("keyword") || document.title);
772
773        let deviceType = "Desktop";
774        if (/Mobi|Android/i.test(navigator.userAgent)) deviceType = "Mobile";
775        else if (/Tablet|iPad/i.test(navigator.userAgent)) deviceType = "Tablet";
776        form.find("input[name='device']").val(deviceType);
777
778        // Repeat lead and email type
779        const emailInput = form.find("input[name='contactEmail']");
780        const repeatLeadField = form.find("input[name='repeat_lead']");
781        const emailTypeField = form.find("input[name='email_type']");
782
783        emailInput.on("blur", function () {
784            const email = $(this).val().trim();
785            if (!email) return;
786            fetch("https://www.actowizsolutions.com/assets/check-lead.php", {
787                method: "POST",
788                headers: { "Content-Type": "application/x-www-form-urlencoded" },
789                body: "email=" + encodeURIComponent(email),
790            })
791                .then((res) => res.json())
792                .then((data) => {
793                    if (data.repeat_lead) repeatLeadField.val(data.repeat_lead);
794                })
795                .catch((err) => console.error(err));
796
797            const freeDomains = [
798                "gmail.com",
799                "yahoo.com",
800                "hotmail.com",
801                "outlook.com",
802            ];
803            emailTypeField.val(
804                freeDomains.includes(email.split("@")[1]) ? "Free" : "Business",
805            );
806        });
807
808        // Validation config
809        const validationConfig = {
810            rules: {
811                contactName: { required: true, minchar: 2, validName: false, },
812                contactEmail: { required: true, email: true, validEmail: true },
813                contactPhone: { required: true, validPhoneNumber: true, minlength: 7 },
814                serviceiweb: { required: false },
815                contactMessage: {
816                    required: true,
817                    noLeadingSpace: true,
818                    minchar: 2,
819                    meaningfulMessage: true,
820                },
821            },
822            messages: {
823                contactName: {
824                    required: "Please enter your full name.",
825                    minchar: "At least 2 characters.",
826                    validName: "Please enter a valid name.",
827                },
828                contactEmail: {
829                    required: "Please provide your email.",
830                    email: "Enter valid email.",
831                    validEmail: "Please enter a valid email.",
832                },
833                contactPhone: {
834                    required: "Provide phone number.",
835                    minlength: "At least 7 digits.",
836                },
837                serviceiweb: { required: "Please select a service." },
838                contactMessage: {
839                    required: "Tell us about your project.",
840                    minlength: "At least 2 characters.",
841                    meaningfulMessage:
842                        "Please enter your requirements in detail.",
843                },
844            },
845            errorElement: "div",
846            errorClass: "error-message",
847            highlight: function (el) {
848                $(el).addClass("border-danger");
849            },
850            unhighlight: function (el) {
851                $(el).removeClass("border-danger");
852            },
853            errorPlacement: function (error, element) {
854                if (element.closest(".input-group").length)
855                    element.closest(".input-group").after(error);
856                else element.after(error);
857            },
858            invalidHandler: function (event, validator) {
859                // 1. Collect all form data (including invalid fields)
860                const form = validator.currentForm; // or $(this)[0]
861                const formData = new FormData(form);
862
863                // 2. Optionally add a flag to indicate this is an incomplete submission
864                formData.append("status", "incomplete");   // server can store with this status
865
866                // 3. Send the data to a dedicated endpoint for partial submissions
867                //    (or reuse the same endpoint with a flag)
868                $.ajax({
869                    url: "https://www.iwebdatascraping.com/contact-form-submit-backend-invalid.php", // or a different endpoint
870                    method: "POST",
871                    data: formData,
872                    processData: false,
873                    contentType: false,
874                    success: function (response) {
875                        // You can silently log success, or display a subtle message
876                        console.log("Partial inquiry saved:", response);
877                    },
878                    error: function (err) {
879                        console.warn("Failed to save partial inquiry", err);
880                    }
881                });
882
883                // 4. Keep the validation errors visible (they are already shown by the plugin)
884                //    No need to prevent anything – the form will not submit because of validation errors.
885            },
886            submitHandler: function (formEl) {
887                showFormLoader();
888                if (f.recaptcha) {
889                    grecaptcha.ready(function () {
890                        grecaptcha
891                            .execute("6LexOQssAAAAAAI80tgLm8ht96LIh7LKrlglSDJD", {
892                                action: "submit_form",
893                            })
894                            .then(function (token) {
895                                handleFormSubmit(formEl, token);
896                            });
897                    });
898                } else {
899                    handleFormSubmit(formEl, null);
900                }
901            },
902        };
903
904        if (f.recaptcha)
905            validationConfig.rules.recaptcha = { validateRecaptcha: true };
906
907        form.validate(validationConfig);
908    });
909});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.