patx/demo

update frontlineboats

Commit 9e212b7 · harrison erd · 2026-06-16T03:02:07-04:00

Changeset
9e212b732801d697fbbb06cb5be96cf195f98afc
Parents
05e4cee328f46f2ada5a9dc666b1414d166268e3

View source at this commit

Comments

No comments yet.

Log in to comment

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">&times;</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>