mirror of
https://github.com/Sea-Haven-Industries/seahaven-site.git
synced 2026-10-03 15:03:15 +00:00
Merge pull request #21 from Sea-Haven-Industries/feature/frontend-polish
Some checks failed
Deploy / deploy (push) Has been cancelled
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:
commit
c31819ab01
22 changed files with 118 additions and 23 deletions
|
|
@ -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"],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
13
_includes/breadcrumb-ld.njk
Normal file
13
_includes/breadcrumb-ld.njk
Normal 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>
|
||||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
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",
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
10
index.njk
10
index.njk
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue