mirror of
https://github.com/Sea-Haven-Industries/seahaven-site.git
synced 2026-09-30 07:43:16 +00:00
Self-host DM Serif Display (regular+italic) and Inter (variable) as latin-subset woff2 (~84KB), with @font-face + font-display:swap and preloads. Removes render-blocking Google Fonts (2 third-party origins, extra DNS/preconnect). reCAPTCHA api.js is no longer eager-loaded in <head>; form.js injects it on first form focus/submit, keeping ~50KB+ of third-party JS off initial load on form pages. CloudFront CSP font-src updated to allow 'self' (additive; gstatic kept for transition).
88 lines
3.1 KiB
JavaScript
88 lines
3.1 KiB
JavaScript
// Basin AJAX submission with lazily-loaded reCAPTCHA v3 for the contact form
|
|
// and job apply forms. The reCAPTCHA script is not loaded until the visitor
|
|
// first interacts with a form (or submits), keeping it off the initial page load.
|
|
(function () {
|
|
"use strict";
|
|
|
|
const SITE_KEY = "6Les66kUAAAAANyLrgkl7iuN4JUpNlB5upaMovI4";
|
|
const forms = document.querySelectorAll(".contact-form form, form.contact-form, form.apply-form");
|
|
if (!forms.length) return;
|
|
|
|
// ── Lazy reCAPTCHA loader ──────────────────────────────────────
|
|
let loadStarted = false;
|
|
function loadRecaptcha() {
|
|
if (loadStarted) return;
|
|
loadStarted = true;
|
|
const s = document.createElement("script");
|
|
s.src = "https://www.google.com/recaptcha/api.js?render=" + SITE_KEY;
|
|
s.async = true;
|
|
document.head.appendChild(s);
|
|
}
|
|
// Resolves once grecaptcha is ready; loads the script if not already loading.
|
|
function readyRecaptcha() {
|
|
loadRecaptcha();
|
|
return new Promise((resolve, reject) => {
|
|
const start = Date.now();
|
|
(function check() {
|
|
if (window.grecaptcha && window.grecaptcha.execute) {
|
|
window.grecaptcha.ready(resolve);
|
|
} else if (Date.now() - start > 10000) {
|
|
reject(new Error("reCAPTCHA failed to load"));
|
|
} else {
|
|
setTimeout(check, 100);
|
|
}
|
|
})();
|
|
});
|
|
}
|
|
|
|
forms.forEach((form) => {
|
|
// Warm the loader as soon as the user engages with the form.
|
|
form.addEventListener("focusin", loadRecaptcha, { once: true });
|
|
|
|
const success = document.getElementById("form-success");
|
|
const statusEl = document.getElementById("form-status"); // optional role="status" region
|
|
const tokenField = form.querySelector("#g-recaptcha-response");
|
|
const btn = form.querySelector('button[type="submit"]');
|
|
const originalLabel = btn ? btn.textContent : "";
|
|
|
|
const announce = (msg) => {
|
|
if (statusEl) statusEl.textContent = msg;
|
|
else if (msg) alert(msg);
|
|
};
|
|
|
|
form.addEventListener("submit", async (e) => {
|
|
e.preventDefault();
|
|
if (btn) {
|
|
btn.textContent = btn.dataset.sending || "Sending…";
|
|
btn.disabled = true;
|
|
}
|
|
announce("");
|
|
|
|
try {
|
|
await readyRecaptcha();
|
|
const token = await window.grecaptcha.execute(SITE_KEY, { action: "submit" });
|
|
if (tokenField) tokenField.value = token;
|
|
const res = await fetch(form.action, {
|
|
method: "POST",
|
|
body: new FormData(form),
|
|
headers: { Accept: "application/json" },
|
|
});
|
|
if (!res.ok) throw new Error("Submission failed");
|
|
form.reset();
|
|
form.style.display = "none";
|
|
if (success) {
|
|
success.style.display = "block";
|
|
success.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
}
|
|
} catch (err) {
|
|
if (btn) {
|
|
btn.textContent = originalLabel;
|
|
btn.disabled = false;
|
|
}
|
|
announce(
|
|
"Something went wrong. Please try again or email us directly at work-orders@seahaven.com"
|
|
);
|
|
}
|
|
});
|
|
});
|
|
})();
|