mirror of
https://github.com/Sea-Haven-Industries/seahaven-site.git
synced 2026-09-30 05:23:18 +00:00
perf: self-host fonts + lazy-load reCAPTCHA
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).
This commit is contained in:
parent
381f6b6dce
commit
87d5dd5d0a
6 changed files with 61 additions and 11 deletions
|
|
@ -21,10 +21,8 @@
|
|||
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/images/favicon-32.png">
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="/assets/images/favicon-192.png">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="preload" as="style" href="https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=Inter:wght@300;400;500;600&display=swap">
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display:ital@0;1&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="preload" href="/assets/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/assets/fonts/dm-serif-display.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="stylesheet" href="/assets/css/main.css">
|
||||
{%- if pageCss %}
|
||||
<link rel="stylesheet" href="/assets/css/{{ pageCss }}.css">
|
||||
|
|
@ -33,9 +31,7 @@
|
|||
<link rel="preload" as="image" href="/assets/images/hero-bg.webp" fetchpriority="high">
|
||||
{%- endif %}
|
||||
{%- block head %}{% endblock %}
|
||||
{%- if recaptcha %}
|
||||
<script src="https://www.google.com/recaptcha/api.js?render=6Les66kUAAAAANyLrgkl7iuN4JUpNlB5upaMovI4"></script>
|
||||
{%- endif %}
|
||||
{#- reCAPTCHA is lazy-loaded by form.js on first form interaction (see recaptcha pages) #}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,29 @@
|
|||
Sea Haven Industries — Shared Styles
|
||||
══════════════════════════════════════════════ */
|
||||
|
||||
/* ── Self-hosted fonts (latin subset) ── */
|
||||
@font-face {
|
||||
font-family: 'DM Serif Display';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/assets/fonts/dm-serif-display.woff2') format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'DM Serif Display';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('/assets/fonts/dm-serif-display-italic.woff2') format('woff2');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 100 900; /* variable */
|
||||
font-display: swap;
|
||||
src: url('/assets/fonts/inter.woff2') format('woff2');
|
||||
}
|
||||
|
||||
/* ── Reset & Tokens ── */
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
|
|
|
|||
BIN
assets/fonts/dm-serif-display-italic.woff2
Normal file
BIN
assets/fonts/dm-serif-display-italic.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/dm-serif-display.woff2
Normal file
BIN
assets/fonts/dm-serif-display.woff2
Normal file
Binary file not shown.
BIN
assets/fonts/inter.woff2
Normal file
BIN
assets/fonts/inter.woff2
Normal file
Binary file not shown.
|
|
@ -1,14 +1,44 @@
|
|||
// Basin AJAX submission with reCAPTCHA v3 for the contact form and job apply forms.
|
||||
// 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";
|
||||
// Contact page wraps the form in a .contact-form div; services puts the class
|
||||
// on the <form> itself; job pages use .apply-form. Match all three.
|
||||
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");
|
||||
|
|
@ -29,7 +59,8 @@
|
|||
announce("");
|
||||
|
||||
try {
|
||||
const token = await grecaptcha.execute(SITE_KEY, { action: "submit" });
|
||||
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",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue