Merge pull request #21 from Sea-Haven-Industries/feature/frontend-polish
Some checks failed
Deploy / deploy (push) Has been cancelled

Frontend polish: self-host fonts, lazy reCAPTCHA, structured data, CSS cleanup
This commit is contained in:
Adam Moussa 2026-06-12 17:28:31 -04:00 • committed by GitHub
commit c31819ab01
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 118 additions and 23 deletions

View file

@ -21,6 +21,9 @@ module.exports = function (eleventyConfig) {
// Current year, for the footer (replaces the deprecated document.write pattern).
eleventyConfig.addShortcode("year", () => `${new Date().getFullYear()}`);
// JSON-encode a value (for safely emitting strings into JSON-LD).
eleventyConfig.addFilter("json", (v) => JSON.stringify(v));
return {
dir: { input: ".", includes: "_includes", data: "_data", output: "_site" },
templateFormats: ["njk"],

View file

@ -49,6 +49,7 @@ npm run build # build to _site/
| Job: Traveling Maintenance/Electrical Assistant | `/careers/traveling-maintenance-assistant/` |
## Changelog
* 12-JUN-2026: Frontend polish — self-hosted fonts (removed render-blocking Google Fonts), lazy-loaded reCAPTCHA, BreadcrumbList JSON-LD on interior pages + richer LocalBusiness (geo/areaServed/hasMap), removed dead CSS classes and `!important` nav hacks.
* 12-JUN-2026: Migrated to a thin Eleventy build — shared chrome (head/nav/footer/JS) deduplicated into one base layout + partials (was hand-copied across 13 pages). Added skip-link + `<main>` landmark, accessible mobile-menu dialog, `:focus-visible`, WCAG AA footer contrast, 1200×630 `og-cover` social image, hero LCP preload, WebP logos, image `width`/`height` (CLS), and pre-deploy CI validation (htmlhint/JSON-LD/sitemap/links). Deploy now builds and syncs `_site/` with split Cache-Control.
* 14-JUL-2025: Initial pull from WordPress site, changed permalinks for proper navigation
* 17-JUL-2025: Add GitHub Actions workflow for S3 sync on push, using AWS OIDC

View file

@ -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>
@ -50,6 +46,10 @@
{% include "footer.njk" %}
{%- if breadcrumbName %}
{% include "breadcrumb-ld.njk" %}
{%- endif %}
<script src="/assets/js/nav.js" defer></script>
{%- if recaptcha %}
<script src="/assets/js/form.js" defer></script>

View file

@ -0,0 +1,13 @@
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Home", "item": "{{ site.url }}/" }
{%- if breadcrumbParent %},
{ "@type": "ListItem", "position": 2, "name": {{ breadcrumbParent.name | json | safe }}, "item": "{{ site.url }}{{ breadcrumbParent.url }}" }
{%- endif %},
{ "@type": "ListItem", "position": {{ 3 if breadcrumbParent else 2 }}, "name": {{ breadcrumbName | json | safe }}, "item": "{{ site.url }}{{ page.url }}" }
]
}
</script>

View file

@ -3,6 +3,7 @@ layout: base.njk
title: "About — Sea Haven Industries"
description: "Learn about Sea Haven Industries — your trusted partner in construction with 20 years of experience, expert technicians, and a commitment to excellence."
pageCss: about
breadcrumbName: "About"
---
<!-- ── Page Hero ── -->
<section class="page-hero">

View file

@ -19,6 +19,14 @@
color: #fff;
}
.site-header:not(.scrolled) .nav-hamburger { color: #fff; }
/* Hover on the transparent (homepage, not-scrolled) nav — these win on
specificity so main.css's hover rules no longer need !important. */
.site-header:not(.scrolled) .nav-links a:hover { color: var(--rose); }
.site-header:not(.scrolled) .nav-cta:hover {
background: var(--rose);
border-color: var(--rose);
color: #fff;
}
/* ── Hero ── */
.hero {

View file

@ -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; }
@ -65,12 +88,6 @@ a { color: inherit; text-decoration: none; }
}
/* ── Typography ── */
.display-xl {
font-family: var(--font-display);
font-size: clamp(2.8rem, 6vw, 5.5rem);
line-height: 1.05;
letter-spacing: -0.02em;
}
.display-lg {
font-family: var(--font-display);
font-size: clamp(2rem, 4vw, 3.5rem);
@ -89,8 +106,6 @@ a { color: inherit; text-decoration: none; }
text-transform: uppercase;
color: var(--rose);
}
.body-lg { font-size: 1.125rem; line-height: 1.7; }
.body-sm { font-size: 0.875rem; line-height: 1.6; color: var(--text-muted); }
/* ── Buttons ── */
.btn {
@ -174,7 +189,7 @@ a { color: inherit; text-decoration: none; }
color: var(--ink-mid);
transition: color 0.15s;
}
.nav-links a:hover { color: var(--rose) !important; }
.nav-links a:hover { color: var(--rose); }
.nav-cta {
font-size: 0.8125rem;
@ -187,9 +202,9 @@ a { color: inherit; text-decoration: none; }
transition: all 0.2s;
}
.nav-cta:hover {
background: var(--rose) !important;
border-color: var(--rose) !important;
color: #fff !important;
background: var(--rose);
border-color: var(--rose);
color: #fff;
}
.nav-hamburger {

Binary file not shown.

Binary file not shown.

BIN
assets/fonts/inter.woff2 Normal file

Binary file not shown.

View file

@ -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",

View file

@ -4,6 +4,8 @@ title: "Dispatcher — Sea Haven Industries"
description: "Apply for the Dispatcher position at Sea Haven Industries. View job details and submit your application today."
pageCss: jobs
recaptcha: true
breadcrumbName: "Dispatcher"
breadcrumbParent: { name: "Careers", url: "/careers/" }
---
<!-- ── Page Hero ── -->
<section class="page-hero">

View file

@ -3,6 +3,7 @@ layout: base.njk
title: "Careers — Sea Haven Industries"
description: "Join the Sea Haven Industries team. Explore careers opportunities and grow with a company committed to staff development and excellence in construction."
pageCss: careers
breadcrumbName: "Careers"
---
<!-- ── Page Hero ── -->
<section class="page-hero careers-hero">

View file

@ -4,6 +4,8 @@ title: "Scheduling Coordinator — Sea Haven Industries"
description: "Apply for the Scheduling Coordinator position at Sea Haven Industries. View job details and submit your application today."
pageCss: jobs
recaptcha: true
breadcrumbName: "Scheduling Coordinator"
breadcrumbParent: { name: "Careers", url: "/careers/" }
---
<!-- ── Page Hero ── -->
<section class="page-hero">

View file

@ -4,6 +4,8 @@ title: "Traveling Maintenance/Electrical Assistant — Sea Haven Industries"
description: "Apply for the Traveling Maintenance/Electrical Assistant position at Sea Haven Industries. View job details and submit your application today."
pageCss: jobs
recaptcha: true
breadcrumbName: "Traveling Maintenance / Electrical Assistant"
breadcrumbParent: { name: "Careers", url: "/careers/" }
---
<!-- ── Page Hero ── -->
<section class="page-hero">

View file

@ -4,6 +4,7 @@ title: "Contact — Sea Haven Industries"
description: "Get in touch with Sea Haven Industries. Have a question or project idea? Contact us today to discuss your construction and facilities needs."
pageCss: contact
recaptcha: true
breadcrumbName: "Contact Us"
---
<!-- ── Page Hero ── -->
<section class="page-hero">

View file

@ -4,6 +4,7 @@ title: "End User License Agreement — Sea Haven Industries"
description: "End User License Agreement for Sea Haven Industries QuickBooks application."
pageCss: legal
robots: "noindex, nofollow"
breadcrumbName: "End User License Agreement"
---
<!-- ── Page Hero ── -->
<section class="page-hero">

View file

@ -307,6 +307,16 @@ permalink: /index.html
"minValue": 120
},
"foundingDate": "2006",
"geo": {
"@type": "GeoCoordinates",
"latitude": 40.8126,
"longitude": -73.1124
},
"areaServed": {
"@type": "Country",
"name": "United States"
},
"hasMap": "https://www.google.com/maps/search/?api=1&query=710+Koehler+Ave+Ronkonkoma+NY+11779",
"serviceType": ["Electrical Work", "Plumbing", "HVACR Services", "Handyman Services", "Garage Door Repair"]
}
</script>

View file

@ -3,6 +3,7 @@ layout: base.njk
title: "Privacy Policy — Sea Haven Industries"
description: "Privacy Policy for Sea Haven Industries. Learn how we collect, use, and protect your personal information."
pageCss: legal
breadcrumbName: "Privacy Policy"
---
<!-- ── Page Hero ── -->
<section class="page-hero">

View file

@ -4,6 +4,7 @@ title: "Services — Sea Haven Industries"
description: "Sea Haven Industries offers reliable electrical, plumbing, HVAC, handyman, and garage door repair services for every need."
pageCss: services
recaptcha: true
breadcrumbName: "Services"
---
<!-- ── Page Hero ── -->
<section class="page-hero">

View file

@ -3,6 +3,7 @@ layout: base.njk
title: "Social Accountability — Sea Haven Industries"
description: "Learn about Sea Haven Industries' commitment to social accountability and responsible business practices in the construction industry."
pageCss: social
breadcrumbName: "Social Accountability"
---
<!-- ── Page Hero ── -->
<section class="page-hero">

View file

@ -3,6 +3,7 @@ layout: base.njk
title: "Terms of Service — Sea Haven Industries"
description: "Terms of Service for Sea Haven Industries. Review the terms and conditions governing your use of the Sea Haven Industries website."
pageCss: legal
breadcrumbName: "Terms of Service"
---
<!-- ── Page Hero ── -->
<section class="page-hero">