patx/demo
update frontlineboats
Commit 9e212b7 · harrison erd · 2026-06-16T03:02:07-04:00
Comments
No comments yet.
Diff
diff --git a/docs/frontlineboats.html b/docs/frontlineboats.html
index c5d7ab8..207b3e4 100644
--- a/docs/frontlineboats.html
+++ b/docs/frontlineboats.html
@@ -69,6 +69,7 @@
}
.nav-links a {
+ position: relative;
font-family: var(--font-heading);
font-size: 0.8rem;
letter-spacing: 0.12em;
@@ -78,7 +79,24 @@
transition: color 0.2s;
}
- .nav-links a:hover { color: var(--flame); }
+ .nav-links a::after {
+ content: '';
+ position: absolute;
+ left: 0;
+ bottom: -0.45rem;
+ width: 28px;
+ height: 2px;
+ background: var(--flame);
+ transform: scaleX(0);
+ transform-origin: left;
+ transition: transform 0.2s ease;
+ }
+
+ .nav-links a:hover,
+ .nav-links a.is-active { color: var(--flame); }
+
+ .nav-links a:hover::after,
+ .nav-links a.is-active::after { transform: scaleX(1); }
.nav-cta {
font-family: var(--font-heading);
@@ -96,6 +114,46 @@
.nav-cta:hover { background: var(--ember); }
+ .nav-toggle {
+ display: none;
+ width: 44px;
+ height: 44px;
+ border: 1px solid var(--steel);
+ background: transparent;
+ color: var(--white);
+ cursor: pointer;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ gap: 5px;
+ }
+
+ .nav-toggle span {
+ width: 20px;
+ height: 2px;
+ background: currentColor;
+ transition: transform 0.2s ease, opacity 0.2s ease;
+ }
+
+ .nav-toggle[aria-expanded="true"] span:nth-child(1) {
+ transform: translateY(7px) rotate(45deg);
+ }
+
+ .nav-toggle[aria-expanded="true"] span:nth-child(2) {
+ opacity: 0;
+ }
+
+ .nav-toggle[aria-expanded="true"] span:nth-child(3) {
+ transform: translateY(-7px) rotate(-45deg);
+ }
+
+ .nav-toggle:focus-visible,
+ .image-modal-close:focus-visible,
+ .project-tile:focus-visible {
+ outline: 2px solid var(--ember);
+ outline-offset: 4px;
+ }
+
/* ── HERO ─────────────────────────────────────────────────────────────── */
.hero {
position: relative;
@@ -268,6 +326,7 @@
/* ── SECTIONS SHARED ──────────────────────────────────────────────────── */
section {
padding: 7rem 5vw;
+ scroll-margin-top: 84px;
}
.section-eyebrow {
@@ -459,6 +518,145 @@
/* ── FLAG SVG ─────────────────────────────────────────────────────────── */
/* Inline thin-red-line flag */
+ /* ── PROJECTS / GALLERY ───────────────────────────────────────────────── */
+ .projects-section {
+ background: #151515;
+ }
+
+ .projects-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: end;
+ gap: 3rem;
+ margin-bottom: 3rem;
+ }
+
+ .projects-header .section-body {
+ max-width: 480px;
+ }
+
+ .projects-mosaic {
+ display: grid;
+ grid-template-columns: 1.1fr 0.9fr 0.9fr;
+ grid-auto-rows: minmax(210px, 26vw);
+ gap: 1px;
+ background: var(--steel);
+ }
+
+ .project-tile {
+ position: relative;
+ min-height: 220px;
+ border: 0;
+ color: var(--white);
+ background: var(--ash);
+ overflow: hidden;
+ cursor: pointer;
+ display: flex;
+ align-items: end;
+ padding: 1.5rem;
+ isolation: isolate;
+ text-align: left;
+ }
+
+ .project-tile.tall {
+ grid-row: span 2;
+ }
+
+ .project-tile::after {
+ content: '';
+ position: absolute;
+ inset: 0;
+ z-index: 0;
+ background:
+ linear-gradient(to top, rgba(10,10,10,0.92) 0%, rgba(10,10,10,0.45) 54%, rgba(10,10,10,0.08) 100%),
+ linear-gradient(135deg, rgba(214,43,43,0.28), transparent 44%);
+ }
+
+ .tile-image {
+ position: absolute;
+ inset: 0;
+ z-index: -1;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ filter: saturate(0.86) contrast(1.04);
+ transform: scale(1.02);
+ transition: transform 0.45s ease, filter 0.45s ease;
+ }
+
+ .project-tile:hover .tile-image {
+ filter: saturate(1) contrast(1.08);
+ transform: scale(1.07);
+ }
+
+ .tile-content {
+ position: relative;
+ z-index: 1;
+ text-shadow: 0 2px 18px rgba(0,0,0,0.45);
+ }
+
+ .tile-tag {
+ font-family: var(--font-heading);
+ font-size: 0.65rem;
+ letter-spacing: 0.18em;
+ text-transform: uppercase;
+ color: var(--flame);
+ margin-bottom: 0.25rem;
+ }
+
+ .tile-title {
+ font-family: var(--font-heading);
+ font-size: 1rem;
+ font-weight: 600;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+ }
+
+ .image-modal {
+ position: fixed;
+ inset: 0;
+ z-index: 1000;
+ display: grid;
+ place-items: center;
+ padding: clamp(1rem, 4vw, 3rem);
+ background: rgba(10,10,10,0.5);
+ backdrop-filter: blur(18px);
+ -webkit-backdrop-filter: blur(18px);
+ opacity: 0;
+ visibility: hidden;
+ pointer-events: none;
+ transition: opacity 0.2s ease, visibility 0.2s ease;
+ }
+
+ .image-modal.is-open {
+ opacity: 1;
+ visibility: visible;
+ pointer-events: auto;
+ }
+
+ .image-modal img {
+ max-width: 100%;
+ max-height: 92vh;
+ object-fit: contain;
+ }
+
+ .image-modal-close {
+ position: fixed;
+ top: 1rem;
+ right: 1rem;
+ width: 44px;
+ height: 44px;
+ border: 0;
+ background: transparent;
+ color: var(--white);
+ font: 300 2.5rem/1 var(--font-body);
+ cursor: pointer;
+ }
+
+ body.modal-open {
+ overflow: hidden;
+ }
+
/* ── PROCESS ──────────────────────────────────────────────────────────── */
.process-section {
background: var(--smoke);
@@ -605,6 +803,10 @@
gap: 1rem;
}
+ .quote-form.is-submitted > :not(#form-success) {
+ display: none;
+ }
+
.form-label {
font-family: var(--font-heading);
font-size: 0.65rem;
@@ -653,6 +855,17 @@
gap: 1rem;
}
+ #form-success {
+ display: none;
+ text-align: center;
+ padding: 1rem;
+ color: var(--flame);
+ font-family: var(--font-heading);
+ font-size: 0.85rem;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+ }
+
/* ── FOOTER ───────────────────────────────────────────────────────────── */
footer {
background: #111;
@@ -682,7 +895,56 @@
/* ── RESPONSIVE ───────────────────────────────────────────────────────── */
@media (max-width: 800px) {
- .nav-links { display: none; }
+ nav {
+ height: auto;
+ min-height: 64px;
+ flex-wrap: wrap;
+ padding: 0.75rem 5vw;
+ }
+
+ .nav-logo {
+ flex: 1;
+ min-width: 0;
+ }
+
+ .nav-toggle {
+ display: flex;
+ }
+
+ .nav-links {
+ display: none;
+ order: 3;
+ width: 100%;
+ flex-direction: column;
+ align-items: stretch;
+ gap: 0;
+ padding-top: 0.75rem;
+ border-top: 1px solid var(--ash);
+ }
+
+ nav.is-open .nav-links {
+ display: flex;
+ }
+
+ .nav-links a {
+ display: block;
+ padding: 0.85rem 0;
+ }
+
+ .nav-links a::after {
+ bottom: 0.55rem;
+ }
+
+ .nav-cta {
+ display: none;
+ order: 4;
+ width: 100%;
+ text-align: center;
+ }
+
+ nav.is-open .nav-cta {
+ display: block;
+ }
.about-section,
.contact-section {
@@ -690,6 +952,26 @@
gap: 3rem;
}
+ .projects-header {
+ display: block;
+ }
+
+ .projects-header .section-body {
+ max-width: none;
+ margin-top: 1rem;
+ }
+
+ .projects-mosaic {
+ grid-template-columns: 1fr;
+ grid-auto-rows: auto;
+ }
+
+ .project-tile,
+ .project-tile.tall {
+ min-height: 260px;
+ grid-row: auto;
+ }
+
.fr-badge { right: 0; bottom: -1rem; }
.process-grid::before { display: none; }
@@ -699,6 +981,53 @@
.trust-divider { display: none; }
}
+ @media (max-width: 560px) {
+ section {
+ padding: 4.5rem 5vw;
+ }
+
+ .hero {
+ min-height: 92vh;
+ padding-bottom: 6vh;
+ }
+
+ .hero-title {
+ font-size: clamp(3.4rem, 18vw, 4.4rem);
+ }
+
+ .hero-actions {
+ align-items: stretch;
+ }
+
+ .btn-primary,
+ .btn-ghost {
+ width: 100%;
+ text-align: center;
+ padding: 0.95rem 1rem;
+ }
+
+ .trust-bar {
+ justify-content: flex-start;
+ }
+
+ .fr-badge {
+ position: relative;
+ right: auto;
+ bottom: auto;
+ margin-top: -1.5rem;
+ width: calc(100% - 1.5rem);
+ }
+
+ .process-step {
+ padding-left: 0;
+ padding-right: 0;
+ }
+
+ footer {
+ align-items: flex-start;
+ }
+ }
+
@media (prefers-reduced-motion: reduce) {
* { transition: none !important; animation: none !important; }
}
@@ -708,10 +1037,16 @@
<!-- NAV -->
<nav>
- <a class="nav-logo" href="#">FRONTLINE <span>BOATS</span></a>
- <ul class="nav-links">
+ <a class="nav-logo" href="#hero">FRONTLINE <span>BOATS</span></a>
+ <button class="nav-toggle" type="button" aria-label="Open navigation" aria-controls="primary-navigation" aria-expanded="false">
+ <span aria-hidden="true"></span>
+ <span aria-hidden="true"></span>
+ <span aria-hidden="true"></span>
+ </button>
+ <ul class="nav-links" id="primary-navigation">
<li><a href="#services">Services</a></li>
<li><a href="#about">About</a></li>
+ <li><a href="#projects">Gallery</a></li>
<li><a href="#process">Process</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
@@ -719,7 +1054,7 @@
</nav>
<!-- HERO -->
- <section class="hero">
+ <section id="hero" class="hero">
<div class="hero-bg"></div>
<div class="hero-slash"></div>
<div class="hero-content">
@@ -877,7 +1212,7 @@
</p>
<p class="section-body" style="margin-top: 1rem;">
Working out of Miami, FL, we serve boaters across South Florida who are tired of
- sloppy installs and chasing electrical gremlins season after season. When we hand
+ sloppy installs and chasing repeat electrical issues season after season. When we hand
your boat back, every system works — and keeps working.
</p>
<div class="about-stat-row">
@@ -897,6 +1232,62 @@
</div>
</section>
+ <!-- PROJECTS / GALLERY -->
+ <section id="projects" class="projects-section">
+ <div class="projects-header">
+ <div>
+ <p class="section-eyebrow">Recent Work</p>
+ <h2 class="section-title">SYSTEMS BUILT<br>FOR THE WATER</h2>
+ </div>
+ <p class="section-body">
+ A closer look at the type of clean installs, helm layouts, wiring work,
+ and marine electronics projects Frontline handles for South Florida boaters.
+ </p>
+ </div>
+
+ <div class="projects-mosaic">
+ <div class="project-tile tall" role="button" tabindex="0" aria-label="View full size image of a center console boat underway">
+ <img class="tile-image" src="https://images.unsplash.com/photo-1605281317010-fe5ffe798166?w=1200&q=82" alt="Center console boat underway in clear water">
+ <div class="tile-content">
+ <div class="tile-tag">Navigation</div>
+ <div class="tile-title">Electronics Ready Helms</div>
+ </div>
+ </div>
+
+ <div class="project-tile" role="button" tabindex="0" aria-label="View full size image of a boat docked at a marina">
+ <img class="tile-image" src="https://images.unsplash.com/photo-1544551763-92ab472cad5d?w=1000&q=82" alt="Boat docked at a marina">
+ <div class="tile-content">
+ <div class="tile-tag">Service</div>
+ <div class="tile-title">Dockside Diagnostics</div>
+ </div>
+ </div>
+
+ <div class="project-tile" role="button" tabindex="0" aria-label="View full size image of marine instruments and controls">
+ <img class="tile-image" src="https://images.unsplash.com/photo-1567899378494-47b22a2ae96a?w=1000&q=82" alt="Marine helm instruments and controls">
+ <div class="tile-content">
+ <div class="tile-tag">Displays</div>
+ <div class="tile-title">Clean Control Layouts</div>
+ </div>
+ </div>
+
+ <div class="project-tile" role="button" tabindex="0" aria-label="View full size image of a powerboat on open water">
+ <img class="tile-image" src="https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?w=1000&q=82" alt="Powerboat running on open water">
+ <div class="tile-content">
+ <div class="tile-tag">Reliability</div>
+ <div class="tile-title">Water-Tested Systems</div>
+ </div>
+ </div>
+
+ <div class="project-tile" role="button" tabindex="0" aria-label="View full size image of a boat at sunset">
+ <img class="tile-image" src="https://images.unsplash.com/photo-1507525428034-b723cf961d3e?w=1000&q=82" alt="Boat silhouetted near sunset on the water">
+ <div class="tile-content">
+ <div class="tile-tag">Audio</div>
+ <div class="tile-title">Weekend Ready Upgrades</div>
+ </div>
+ </div>
+ </div>
+ </section>
+
<!-- PROCESS -->
<section id="process" class="process-section">
<p class="section-eyebrow">How It Works</p>
@@ -984,7 +1375,7 @@
</div>
<div>
- <div class="quote-form">
+ <form class="quote-form" onsubmit="handleSubmit(event)">
<div class="form-row">
<div class="form-field">
<label class="form-label" for="fname">First Name</label>
@@ -1020,8 +1411,9 @@
<label class="form-label" for="message">Tell Us About Your Boat</label>
<textarea class="form-textarea" id="message" placeholder="Year, make, model, and what you're looking to have done…"></textarea>
</div>
- <button class="btn-primary" type="button" onclick="handleSubmit()" style="width:100%; font-size:0.85rem; padding: 1.1rem;">Request a Free Quote</button>
- </div>
+ <button class="btn-primary" type="submit" style="width:100%; font-size:0.85rem; padding: 1.1rem;">Request a Free Quote</button>
+ <div id="form-success">Message sent. We'll be in touch shortly.</div>
+ </form>
</div>
</section>
@@ -1032,31 +1424,150 @@
<div class="footer-note">© 2025 Frontline Boat Services. All rights reserved.</div>
</footer>
+ <div class="image-modal" id="image-modal" role="dialog" aria-modal="true" aria-hidden="true">
+ <button class="image-modal-close" type="button" aria-label="Close full screen image">×</button>
+ <img id="image-modal-img" src="" alt="">
+ </div>
+
<script>
- // Minimal smooth scroll for nav links
- document.querySelectorAll('a[href^="#"]').forEach(link => {
- link.addEventListener('click', e => {
- const target = document.querySelector(link.getAttribute('href'));
- if (target) {
- e.preventDefault();
- target.scrollIntoView({ behavior: 'smooth' });
+ function handleSubmit(e) {
+ e.preventDefault();
+ const form = e.target;
+ const successMessage = document.getElementById('form-success');
+
+ form.reset();
+ form.classList.add('is-submitted');
+ form.querySelectorAll('input, select, textarea, button[type="submit"]').forEach((control) => {
+ control.disabled = true;
+ });
+ successMessage.style.display = 'block';
+ successMessage.setAttribute('tabindex', '-1');
+ successMessage.focus();
+ }
+
+ const siteNav = document.querySelector('nav');
+ const navToggle = document.querySelector('.nav-toggle');
+ const navSectionLinks = Array.from(document.querySelectorAll('.nav-links a[href^="#"]'));
+ const navSections = navSectionLinks
+ .map((link) => ({ link, section: document.querySelector(link.getAttribute('href')) }))
+ .filter(({ section }) => section);
+
+ function setMobileNav(open) {
+ siteNav.classList.toggle('is-open', open);
+ navToggle.setAttribute('aria-expanded', String(open));
+ navToggle.setAttribute('aria-label', open ? 'Close navigation' : 'Open navigation');
+ }
+
+ function setActiveNavLink(activeId) {
+ navSections.forEach(({ link, section }) => {
+ const isActive = section.id === activeId;
+ link.classList.toggle('is-active', isActive);
+
+ if (isActive) {
+ link.setAttribute('aria-current', 'location');
+ } else {
+ link.removeAttribute('aria-current');
}
});
+ }
+
+ function updateActiveNavLink() {
+ const navOffset = siteNav.offsetHeight + 24;
+ let activeId = '';
+
+ navSections.forEach(({ section }) => {
+ if (section.getBoundingClientRect().top <= navOffset) {
+ activeId = section.id;
+ }
+ });
+
+ setActiveNavLink(activeId);
+ siteNav.style.boxShadow = window.scrollY > 40 ? '0 4px 24px rgba(0,0,0,0.5)' : 'none';
+ }
+
+ navToggle.addEventListener('click', () => {
+ setMobileNav(!siteNav.classList.contains('is-open'));
});
- // Sticky nav shadow
- const nav = document.querySelector('nav');
- window.addEventListener('scroll', () => {
- nav.style.boxShadow = window.scrollY > 40 ? '0 4px 24px rgba(0,0,0,0.5)' : 'none';
+ document.querySelectorAll('.nav-links a, .nav-cta').forEach((link) => {
+ link.addEventListener('click', () => {
+ const targetId = link.getAttribute('href')?.slice(1);
+
+ if (targetId) {
+ setActiveNavLink(targetId);
+ }
+
+ setMobileNav(false);
+ });
});
- // Form submit placeholder
- function handleSubmit() {
- const btn = document.querySelector('.quote-form .btn-primary');
- btn.textContent = '✓ Message Sent — We\'ll be in touch!';
- btn.style.background = '#1a5c1a';
- btn.disabled = true;
+ window.addEventListener('scroll', updateActiveNavLink, { passive: true });
+ window.addEventListener('resize', updateActiveNavLink);
+ window.addEventListener('load', updateActiveNavLink);
+ updateActiveNavLink();
+
+ const modal = document.getElementById('image-modal');
+ const modalImage = document.getElementById('image-modal-img');
+ const closeModalButton = modal.querySelector('.image-modal-close');
+ let lastFocusedElement = null;
+
+ function openImageModal(image) {
+ lastFocusedElement = document.activeElement;
+ modalImage.src = image.currentSrc || image.src;
+ modalImage.alt = image.alt;
+ modal.classList.add('is-open');
+ modal.setAttribute('aria-hidden', 'false');
+ document.body.classList.add('modal-open');
+ closeModalButton.focus();
+ }
+
+ function closeImageModal() {
+ modal.classList.remove('is-open');
+ modal.setAttribute('aria-hidden', 'true');
+ document.body.classList.remove('modal-open');
+ modalImage.removeAttribute('src');
+ modalImage.alt = '';
+
+ if (lastFocusedElement) {
+ lastFocusedElement.focus();
+ }
}
+
+ document.querySelectorAll('.project-tile').forEach((tile) => {
+ tile.addEventListener('click', () => {
+ openImageModal(tile.querySelector('.tile-image'));
+ });
+
+ tile.addEventListener('keydown', (event) => {
+ if (event.key === 'Enter' || event.key === ' ') {
+ event.preventDefault();
+ openImageModal(tile.querySelector('.tile-image'));
+ }
+ });
+ });
+
+ closeModalButton.addEventListener('click', closeImageModal);
+
+ modal.addEventListener('click', (event) => {
+ if (event.target === modal) {
+ closeImageModal();
+ }
+ });
+
+ document.addEventListener('keydown', (event) => {
+ if (event.key !== 'Escape') {
+ return;
+ }
+
+ if (modal.classList.contains('is-open')) {
+ closeImageModal();
+ }
+
+ if (siteNav.classList.contains('is-open')) {
+ setMobileNav(false);
+ navToggle.focus();
+ }
+ });
</script>
</body>
-</html>
\ No newline at end of file
+</html>