fix: restructure video as global backdrop for iOS notch extension
All checks were successful
build-website / build (push) Successful in 1m31s

- Created VideoBackdrop component as global sibling layer
- Video now sits at app level behind all content (z-index: 0)
- Hero section made transparent to reveal video backdrop
- Video extends into notch with negative top positioning
- Content remains in safe areas with proper padding
- Removed video container from HeroSection component
- Added app.vue to manage global layout structure

This separation allows the video to truly extend edge-to-edge
including under the iOS notch/Dynamic Island while keeping
all interactive content within safe areas.
This commit is contained in:
2025-09-21 13:48:03 +02:00
parent bc3ebcb558
commit ed92c458a9
4 changed files with 235 additions and 111 deletions

View File

@@ -1,36 +1,7 @@
<template>
<section class="hero-voyage" id="heroSection">
<div ref="videoContainer" class="hero-video-container">
<!-- White overlay that fades out when video is ready -->
<div
class="video-white-overlay"
:class="{ 'fade-out': videoLoaded }"
/>
<video
ref="videoElement"
autoplay
loop
muted
playsinline
preload="auto"
class="hero-video"
:class="{ 'video-loaded': videoLoaded }"
@loadeddata="handleVideoLoaded"
@canplaythrough="handleVideoLoaded"
@play="handleVideoLoaded"
>
<source
src="https://videos.pexels.com/video-files/3571264/3571264-uhd_2560_1440_30fps.mp4"
type="video/mp4"
>
</video>
<div class="hero-overlay gradient-warm" />
<div class="hero-overlay gradient-depth" />
</div>
<div class="hero-content" :class="{ 'is-ready': videoLoaded }">
<!-- Video is now handled by VideoBackdrop component -->
<div class="hero-content">
<div class="hero-logo animate-fade-in">
<img src="/HARBOR-SMITH-white.png" alt="Harbor Smith" width="400" height="250">
</div>
@@ -65,25 +36,16 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useParallax } from '~/composables/useParallax'
import { useIntersectionAnimations } from '~/composables/useIntersectionAnimations'
import { useRipple } from '~/composables/useRipple'
import { useSmoothScroll } from '~/composables/useSmoothScroll'
const { scrollToElement } = useSmoothScroll()
const videoLoaded = ref(false)
const videoContainer = ref<HTMLElement | null>(null)
const videoElement = ref<HTMLVideoElement | null>(null)
const animatedCount = ref(0)
useParallax(videoContainer, 0.5)
useIntersectionAnimations()
useRipple()
const handleVideoLoaded = () => {
videoLoaded.value = true
}
const handlePhoneClick = () => {
window.location.href = 'tel:510-701-2535'
}
@@ -96,26 +58,6 @@ const handleScrollToExplore = () => {
scrollToElement('#services', 800) // Constant-speed scroll to services
}
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) => {
@@ -156,53 +98,21 @@ const animateCount = () => {
}
onMounted(() => {
handleVideoVisibility()
initSmoothScroll()
animateCount()
// Fallback: ensure video becomes visible after a delay
setTimeout(() => {
if (!videoLoaded.value) {
videoLoaded.value = true
}
}, 1500)
})
</script>
<style scoped>
/* Video fade-in transition to prevent flash */
.hero-video {
opacity: 0;
transition: opacity 1s ease-in-out;
}
.hero-video.video-loaded {
opacity: 1;
}
/* White overlay for smooth video loading transition */
.video-white-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(180deg, #ffffff 0%, #f8f9fa 100%);
z-index: 2;
opacity: 1;
transition: opacity 800ms ease-out;
will-change: opacity;
}
.video-white-overlay.fade-out {
opacity: 0;
pointer-events: none;
}
/* Container to prevent horizontal overflow */
/* Hero section is now transparent to show video backdrop behind it */
.hero-voyage {
position: relative;
background: transparent;
overflow-x: hidden !important;
max-width: 100vw !important;
/* Ensure hero takes up full viewport height */
min-height: 100vh;
min-height: 100dvh;
}
/* Scroll to explore indicator */