176 lines
4.2 KiB
Vue
176 lines
4.2 KiB
Vue
<template>
|
|
<nav
|
|
id="voyageNav"
|
|
:class="['voyage-nav', { scrolled: isScrolled }]"
|
|
>
|
|
<div class="nav-container">
|
|
<div class="nav-brand" @click="scrollToTop" style="cursor: pointer;">
|
|
<img
|
|
:src="navLogo"
|
|
:alt="logoAlt"
|
|
class="nav-logo"
|
|
id="navLogo"
|
|
width="150"
|
|
height="50"
|
|
>
|
|
<span>HARBOR SMITH</span>
|
|
</div>
|
|
<div class="nav-links">
|
|
<a
|
|
v-for="link in navLinks"
|
|
:key="link.href"
|
|
:href="link.href"
|
|
class="nav-link"
|
|
@click="handleSmoothScroll($event, link.href)"
|
|
>
|
|
{{ link.label }}
|
|
</a>
|
|
<a
|
|
href="tel:510-701-2535"
|
|
class="nav-link nav-cta"
|
|
>
|
|
Call Now
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
|
import { useSmoothScroll } from '~/composables/useSmoothScroll'
|
|
|
|
const { scrollToElement, scrollToTop: smoothScrollToTop } = useSmoothScroll()
|
|
const isScrolled = ref(false)
|
|
|
|
const navLinks = [
|
|
{ href: '#services', label: 'Services' },
|
|
{ href: '#testimonials', label: 'Testimonials' },
|
|
{ href: '#contact', label: 'Contact' }
|
|
]
|
|
|
|
const navLogo = computed(() =>
|
|
isScrolled.value ? '/HARBOR-SMITH_navy.png' : '/HARBOR-SMITH-white.png'
|
|
)
|
|
|
|
const logoAlt = computed(() =>
|
|
isScrolled.value ? 'Harbor Smith Navy Logo' : 'Harbor Smith White Logo'
|
|
)
|
|
|
|
let maxSafeAreaTop = 0
|
|
|
|
const measureSafeAreaTop = () => {
|
|
if (typeof window === 'undefined') {
|
|
return 0
|
|
}
|
|
|
|
const viewport = window.visualViewport
|
|
const viewportInset = viewport?.offsetTop ?? 0
|
|
let envInset = 0
|
|
let legacyInset = 0
|
|
|
|
if (document.body) {
|
|
const probe = document.createElement('div')
|
|
probe.style.cssText = [
|
|
'position:absolute',
|
|
'top:0',
|
|
'left:0',
|
|
'width:0',
|
|
'height:env(safe-area-inset-top, 0px)',
|
|
'pointer-events:none',
|
|
'visibility:hidden'
|
|
].join(';')
|
|
|
|
document.body.appendChild(probe)
|
|
envInset = probe.getBoundingClientRect().height || 0
|
|
|
|
probe.style.height = 'constant(safe-area-inset-top)'
|
|
legacyInset = probe.getBoundingClientRect().height || 0
|
|
document.body.removeChild(probe)
|
|
}
|
|
|
|
return Math.max(0, viewportInset, envInset, legacyInset)
|
|
}
|
|
|
|
const setInitialSafeAreaTop = () => {
|
|
if (typeof window === 'undefined') {
|
|
return
|
|
}
|
|
|
|
const root = document.documentElement
|
|
const measured = measureSafeAreaTop()
|
|
|
|
if (measured > maxSafeAreaTop) {
|
|
maxSafeAreaTop = measured
|
|
root.style.setProperty('--initial-safe-area-top', `${measured}px`)
|
|
}
|
|
}
|
|
|
|
const resetSafeAreaTop = () => {
|
|
if (typeof window === 'undefined') {
|
|
return
|
|
}
|
|
|
|
maxSafeAreaTop = 0
|
|
document.documentElement.style.setProperty('--initial-safe-area-top', '0px')
|
|
const remeasure = () => {
|
|
setInitialSafeAreaTop()
|
|
}
|
|
|
|
if (typeof requestAnimationFrame === 'function') {
|
|
requestAnimationFrame(() => {
|
|
requestAnimationFrame(remeasure)
|
|
})
|
|
} else {
|
|
setTimeout(remeasure, 50)
|
|
}
|
|
}
|
|
|
|
const handleScroll = () => {
|
|
isScrolled.value = window.pageYOffset > 50
|
|
}
|
|
|
|
const handleSmoothScroll = (event: Event, href: string) => {
|
|
if (!href.startsWith('#')) {
|
|
return
|
|
}
|
|
|
|
event.preventDefault()
|
|
scrollToElement(href, 800) // Constant-speed smooth scrolling
|
|
}
|
|
|
|
const scrollToTop = () => {
|
|
smoothScrollToTop(800) // Constant-speed smooth scrolling
|
|
}
|
|
|
|
onMounted(() => {
|
|
setInitialSafeAreaTop()
|
|
setTimeout(() => {
|
|
setInitialSafeAreaTop()
|
|
}, 100)
|
|
handleScroll()
|
|
window.addEventListener('scroll', handleScroll, { passive: true })
|
|
window.addEventListener('orientationchange', resetSafeAreaTop)
|
|
window.addEventListener('resize', setInitialSafeAreaTop)
|
|
|
|
if (window.visualViewport) {
|
|
window.visualViewport.addEventListener('resize', setInitialSafeAreaTop)
|
|
}
|
|
})
|
|
onBeforeUnmount(() => {
|
|
window.removeEventListener('scroll', handleScroll)
|
|
window.removeEventListener('orientationchange', resetSafeAreaTop)
|
|
window.removeEventListener('resize', setInitialSafeAreaTop)
|
|
|
|
if (window.visualViewport) {
|
|
window.visualViewport.removeEventListener('resize', setInitialSafeAreaTop)
|
|
}
|
|
|
|
maxSafeAreaTop = 0
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* Navigation styling handled in voyage-layout.css */
|
|
</style>
|