/* ============================================================ Pine Creek — site interactions - Mobile nav (hamburger) toggle - Smooth-scroll for in-page anchors with fixed-header offset - Image galleries (prev/next, dots, autoplay, swipe) - Sticky-nav shadow on scroll ============================================================ */ const hamburger = document.getElementById('hamburger'), mainNav = document.querySelector('#mainNav ul'), navItem = document.getElementsByClassName('nav-item') // --- Mobile nav ------------------------------------------------ const toggleNav = () => { mainNav.classList.toggle('in') hamburger.classList.toggle('in') }, closeNav = () => { mainNav.classList.remove('in') hamburger.classList.remove('in') } hamburger.onclick = toggleNav hamburger.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggleNav() } }) // --- Smooth scroll with fixed-header offset -------------------- const scrollToTarget = (hash) => { const target = hash && document.querySelector(hash) if (!target) return false const header = document.getElementById('mainNav') const headerHeight = header ? header.offsetHeight : 0 const y = window.pageYOffset + target.getBoundingClientRect().top - headerHeight - 8 window.scrollTo({ top: y, behavior: 'smooth' }) return true } for (let i = 0; i < navItem.length; i++) { navItem[i].onclick = function (e) { const href = this.getAttribute('href') || '' closeNav() if (href.startsWith('#') && scrollToTarget(href)) { e.preventDefault() } } } // Also offset any in-page anchor (hero buttons, etc.) document.querySelectorAll('a[href^="#"]:not(.nav-item)').forEach((a) => { a.addEventListener('click', (e) => { const href = a.getAttribute('href') if (href.length > 1 && scrollToTarget(href)) e.preventDefault() }) }) window.addEventListener('load', () => scrollToTarget(location.hash)) window.addEventListener('hashchange', () => scrollToTarget(location.hash)) // --- Sticky nav shadow ---------------------------------------- const navEl = document.getElementById('mainNav') const onScroll = () => { if (window.pageYOffset > 20) navEl.classList.add('scrolled') else navEl.classList.remove('scrolled') } window.addEventListener('scroll', onScroll, { passive: true }) onScroll() // --- Lightbox -------------------------------------------------- const lightbox = document.getElementById('lightbox') let lbSrcs = [], lbAlts = [], lbIndex = 0 if (lightbox) { const lbImg = lightbox.querySelector('.lightbox-img'), lbCap = lightbox.querySelector('.lightbox-caption'), lbPrev = lightbox.querySelector('.lightbox-arrow.prev'), lbNext = lightbox.querySelector('.lightbox-arrow.next'), lbClose = lightbox.querySelector('.lightbox-close') const render = () => { lbImg.src = lbSrcs[lbIndex] lbImg.alt = lbAlts[lbIndex] || '' lbCap.textContent = lbAlts[lbIndex] || '' } window.openLightbox = (srcs, alts, i) => { lbSrcs = srcs; lbAlts = alts; lbIndex = i render() lightbox.hidden = false document.body.style.overflow = 'hidden' } const close = () => { lightbox.hidden = true; document.body.style.overflow = '' } const lbGo = (n) => { lbIndex = (n + lbSrcs.length) % lbSrcs.length; render() } lbPrev.addEventListener('click', (e) => { e.stopPropagation(); lbGo(lbIndex - 1) }) lbNext.addEventListener('click', (e) => { e.stopPropagation(); lbGo(lbIndex + 1) }) lbClose.addEventListener('click', close) lightbox.addEventListener('click', (e) => { if (e.target === lbImg) lbGo(lbIndex + 1) else if (!e.target.closest('.lightbox-arrow') && !e.target.closest('.lightbox-close')) close() }) document.addEventListener('keydown', (e) => { if (lightbox.hidden) return if (e.key === 'Escape') close() else if (e.key === 'ArrowLeft') lbGo(lbIndex - 1) else if (e.key === 'ArrowRight') lbGo(lbIndex + 1) }) } // --- Galleries ------------------------------------------------- document.querySelectorAll('.gallery').forEach((gallery) => { const imgs = Array.from(gallery.querySelectorAll('.gallery-img')), prev = gallery.querySelector('.gallery-arrow.prev'), next = gallery.querySelector('.gallery-arrow.next'), dotsWrap = gallery.querySelector('.gallery-dots') if (!imgs.length) return let index = 0 let timer = null // Build dots const dots = imgs.map((_, i) => { const dot = document.createElement('button') dot.className = 'gallery-dot' + (i === 0 ? ' is-active' : '') dot.setAttribute('aria-label', 'Go to image ' + (i + 1)) dot.addEventListener('click', () => { go(i); restart() }) dotsWrap && dotsWrap.appendChild(dot) return dot }) const go = (n) => { imgs[index].classList.remove('is-active') dots[index] && dots[index].classList.remove('is-active') index = (n + imgs.length) % imgs.length imgs[index].classList.add('is-active') dots[index] && dots[index].classList.add('is-active') } const start = () => { timer = setInterval(() => go(index + 1), 6000) } const stop = () => { clearInterval(timer); timer = null } const restart = () => { stop(); start() } prev && prev.addEventListener('click', () => { go(index - 1); restart() }) next && next.addEventListener('click', () => { go(index + 1); restart() }) // Pause on hover (desktop) gallery.addEventListener('mouseenter', stop) gallery.addEventListener('mouseleave', start) // Touch swipe let startX = 0 gallery.addEventListener('touchstart', (e) => { startX = e.touches[0].clientX; stop() }, { passive: true }) gallery.addEventListener('touchend', (e) => { const dx = e.changedTouches[0].clientX - startX if (Math.abs(dx) > 40) go(index + (dx < 0 ? 1 : -1)) start() }) // Click image to open in lightbox (opens at the currently shown slide) const track = gallery.querySelector('.gallery-track') if (track && window.openLightbox) { const srcs = imgs.map((i) => i.getAttribute('src')) const alts = imgs.map((i) => i.alt) track.classList.add('zoomable') track.addEventListener('click', () => window.openLightbox(srcs, alts, index)) } start() }) // --- Footer year ---------------------------------------------- const yearEl = document.getElementById('year') if (yearEl) yearEl.textContent = new Date().getFullYear()