Initial import of HarborSmith website
Some checks failed
build-website / build (push) Failing after 1m2s
Some checks failed
build-website / build (push) Failing after 1m2s
This commit is contained in:
148
apps/website/components/HeroSection.vue
Normal file
148
apps/website/components/HeroSection.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<section class="hero-voyage" id="heroSection">
|
||||
<div ref="videoContainer" class="hero-video-container">
|
||||
<video
|
||||
ref="videoElement"
|
||||
autoplay
|
||||
loop
|
||||
muted
|
||||
playsinline
|
||||
class="hero-video"
|
||||
@loadeddata="handleVideoLoaded"
|
||||
>
|
||||
<source
|
||||
src="https://videos.pexels.com/video-files/3571264/3571264-uhd_2560_1440_30fps.mp4"
|
||||
type="video/mp4"
|
||||
>
|
||||
</video>
|
||||
|
||||
<div
|
||||
v-if="!videoLoaded"
|
||||
class="hero-image-fallback"
|
||||
:style="{ backgroundImage: 'url(/golden_gate.jpg)' }"
|
||||
/>
|
||||
|
||||
<div class="hero-overlay gradient-warm" />
|
||||
<div class="hero-overlay gradient-depth" />
|
||||
</div>
|
||||
|
||||
<div class="hero-content">
|
||||
<div class="hero-logo animate-fade-in">
|
||||
<img src="/HARBOR-SMITH-white.png" alt="Harbor Smith" style="height: 250px; margin: 40px 0;">
|
||||
</div>
|
||||
|
||||
<div class="trust-badge animate-fade-in">
|
||||
<div class="stars">
|
||||
<span class="stars-icons">
|
||||
<LucideStar class="star-filled" />
|
||||
<LucideStar class="star-filled" />
|
||||
<LucideStar class="star-filled" />
|
||||
<LucideStar class="star-filled" />
|
||||
<LucideStar class="star-filled" />
|
||||
</span>
|
||||
</div>
|
||||
<span>Trusted by 0+ seafarers</span>
|
||||
</div>
|
||||
|
||||
<p class="hero-subtext animate-fade-up-delay">
|
||||
<span style="font-size: 1.5rem; font-weight: 500; text-transform: none; letter-spacing: normal; margin-bottom: 10px; display: block;">
|
||||
Personalized Service Maintenance for Your Boat
|
||||
</span>
|
||||
Keep your vessel pristine with San Francisco Bay's premier mobile boat maintenance service.
|
||||
</p>
|
||||
|
||||
<div class="hero-actions animate-fade-up-delay-2">
|
||||
<button class="btn-primary-warm" @click="handlePhoneClick">
|
||||
<LucidePhone class="btn-icon" />
|
||||
Call (510) 701-2535
|
||||
</button>
|
||||
<button class="btn-secondary-warm" @click="handleServicesClick">
|
||||
<LucideWrench class="btn-icon" />
|
||||
View Our Services
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="scroll-indicator">
|
||||
<span>Scroll to explore</span>
|
||||
<div class="scroll-arrow">
|
||||
<LucideChevronDown />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useParallax } from '~/composables/useParallax'
|
||||
import { useIntersectionAnimations } from '~/composables/useIntersectionAnimations'
|
||||
import { useRipple } from '~/composables/useRipple'
|
||||
|
||||
const videoLoaded = ref(false)
|
||||
const videoContainer = ref<HTMLElement | null>(null)
|
||||
const videoElement = ref<HTMLVideoElement | null>(null)
|
||||
|
||||
useParallax(videoContainer, 0.5)
|
||||
useIntersectionAnimations()
|
||||
useRipple()
|
||||
|
||||
const handleVideoLoaded = () => {
|
||||
videoLoaded.value = true
|
||||
}
|
||||
|
||||
const handlePhoneClick = () => {
|
||||
window.location.href = 'tel:510-701-2535'
|
||||
}
|
||||
|
||||
const handleServicesClick = () => {
|
||||
const element = document.querySelector('#services')
|
||||
if (element) {
|
||||
element.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
}
|
||||
|
||||
const handleVideoVisibility = () => {
|
||||
if (!videoElement.value) {
|
||||
return
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
videoElement.value?.play()
|
||||
} else {
|
||||
videoElement.value?.pause()
|
||||
}
|
||||
})
|
||||
}, { threshold: 0.25 })
|
||||
|
||||
observer.observe(videoElement.value)
|
||||
|
||||
return () => observer.disconnect()
|
||||
}
|
||||
|
||||
const initSmoothScroll = () => {
|
||||
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
|
||||
anchor.addEventListener('click', (event) => {
|
||||
event.preventDefault()
|
||||
const href = anchor.getAttribute('href')
|
||||
if (!href) {
|
||||
return
|
||||
}
|
||||
const target = document.querySelector(href)
|
||||
if (target) {
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
handleVideoVisibility()
|
||||
initSmoothScroll()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Additional animations defined in voyage-layout.css */
|
||||
</style>
|
||||
Reference in New Issue
Block a user