fix: implement Vue 3 Teleport to bypass DOM containment for iOS video extension
All checks were successful
build-website / build (push) Successful in 1m49s

- Wrapped Teleport in ClientOnly to avoid SSR/hydration issues
- Replaced env() with hardcoded -59px for Dynamic Island (Safari bug workaround)
- Added !important flags to ensure styles aren't overridden
- Set overflow: visible on hero-voyage container
- Used z-index: 0 to ensure video stays behind content

This addresses the Nuxt SSG + Teleport incompatibility and iOS Safari's env() bug

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-21 18:51:34 +02:00
parent 4dcfea96ce
commit f94e3584ce

View File

@@ -1,37 +1,40 @@
<template> <template>
<section class="hero-voyage" id="heroSection"> <section class="hero-voyage" id="heroSection">
<!-- Hero video background teleported to body for proper safe area extension --> <!-- Hero video background teleported to body for proper safe area extension -->
<Teleport to="body"> <!-- Using ClientOnly to avoid SSR/hydration issues with Teleport -->
<div ref="videoContainer" class="hero-video-container hero-video-teleported"> <ClientOnly>
<!-- White overlay that fades out when video is ready --> <Teleport to="body">
<div <div ref="videoContainer" class="hero-video-container hero-video-teleported">
class="video-white-overlay" <!-- White overlay that fades out when video is ready -->
:class="{ 'fade-out': videoLoaded }" <div
/> class="video-white-overlay"
:class="{ 'fade-out': videoLoaded }"
/>
<video <video
ref="videoElement" ref="videoElement"
autoplay autoplay
loop loop
muted muted
playsinline playsinline
preload="auto" preload="auto"
class="hero-video" class="hero-video"
:class="{ 'video-loaded': videoLoaded }" :class="{ 'video-loaded': videoLoaded }"
@loadeddata="handleVideoLoaded" @loadeddata="handleVideoLoaded"
@canplaythrough="handleVideoLoaded" @canplaythrough="handleVideoLoaded"
@play="handleVideoLoaded" @play="handleVideoLoaded"
>
<source
src="https://videos.pexels.com/video-files/3571264/3571264-uhd_2560_1440_30fps.mp4"
type="video/mp4"
> >
</video> <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-warm" />
<div class="hero-overlay gradient-depth" /> <div class="hero-overlay gradient-depth" />
</div> </div>
</Teleport> </Teleport>
</ClientOnly>
<div class="hero-content" :class="{ 'is-ready': videoLoaded }"> <div class="hero-content" :class="{ 'is-ready': videoLoaded }">
<div class="hero-main"> <div class="hero-main">
@@ -174,50 +177,36 @@ onMounted(() => {
</script> </script>
<style scoped> <style scoped>
/* Hero video container - Safari-compatible safe area implementation */ /* Hero video container - Hardcoded safe area implementation for iOS */
.hero-video-container { .hero-video-container {
position: fixed; position: fixed !important;
left: 0; left: 0 !important;
right: 0; right: 0 !important;
width: 100vw; width: 100vw !important;
/* Default fallback for devices without notch */ /* Hardcoded for iPhone 14/15 Pro Dynamic Island */
top: 0; /* Using negative value to extend into safe area */
height: 100vh; top: -59px !important;
z-index: 1; height: calc(100vh + 59px) !important;
pointer-events: none; z-index: 0 !important;
pointer-events: none !important;
background: #ffffff; background: #ffffff;
} }
/* Teleported video container - positioned at body level to bypass DOM containment */ /* Teleported video container - ensure it stays at body level */
.hero-video-teleported { .hero-video-teleported {
position: fixed !important; position: fixed !important;
z-index: 1 !important; z-index: 0 !important;
/* Force the video to extend beyond safe area boundaries */
top: -59px !important;
height: calc(100vh + 59px) !important;
} }
/* Use @supports with max() as recommended by Apple for safe area */ /* Fallback for older iPhones with smaller notch */
@supports(padding: max(0px)) { @media screen and (max-height: 812px) {
.hero-video-container { .hero-video-container,
/* Use max() to provide fallback when env() returns 0 (Safari bug) */ .hero-video-teleported {
top: calc(-1 * max(env(safe-area-inset-top), 20px)); top: -44px !important;
height: calc(100vh + max(env(safe-area-inset-top), 20px)); height: calc(100vh + 44px) !important;
}
}
/* iOS-specific fallback using -webkit-touch-callout detection */
@supports (-webkit-touch-callout: none) {
.hero-video-container {
/* Hardcoded fallback for iPhone 14/15 Pro Dynamic Island (59px) */
/* and iPhone X-13 notch (44px) - uses larger value for safety */
top: -59px;
height: calc(100vh + 59px);
}
/* Try env() with both modern and legacy syntax */
@supports(padding: max(0px)) {
.hero-video-container {
top: calc(-1 * max(constant(safe-area-inset-top), env(safe-area-inset-top), 59px));
height: calc(100vh + max(constant(safe-area-inset-top), env(safe-area-inset-top), 59px));
}
} }
} }
@@ -278,6 +267,8 @@ onMounted(() => {
max-width: 100vw !important; max-width: 100vw !important;
position: relative; position: relative;
min-height: 100vh; min-height: 100vh;
/* Explicitly allow overflow to ensure video can extend */
overflow: visible !important;
} }
.hero-main { .hero-main {