fix: Safari workaround for env() returning 0 in portrait mode
All checks were successful
build-website / build (push) Successful in 1m50s

- Add hardcoded fallbacks using max() function per Apple's recommendation
- Support both constant() and env() for legacy compatibility
- Use 59px fallback for Dynamic Island devices
- Add iOS-specific detection with @supports(-webkit-touch-callout)
- Create improved test page with device detection
- Update hero-content padding with same fallback approach

This works around the known Safari bug where env(safe-area-inset-top)
returns 0px in portrait mode. The video will now extend 59px above the
viewport on Dynamic Island iPhones, ensuring it covers the notch area.
This commit is contained in:
2025-09-21 18:29:20 +02:00
parent 69e4a22354
commit 5e7ac5fbf6
3 changed files with 317 additions and 15 deletions

View File

@@ -490,11 +490,11 @@ html {
align-items: center;
justify-items: center;
text-align: center;
/* Use env() directly for safe area padding */
padding-top: calc(env(safe-area-inset-top) + clamp(2rem, 8vh, 5rem));
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
padding-bottom: calc(env(safe-area-inset-bottom) + clamp(1.25rem, 3vh, 2.5rem));
/* Safari-compatible safe area padding with fallbacks */
padding-top: calc(max(env(safe-area-inset-top), 44px) + clamp(2rem, 8vh, 5rem));
padding-left: max(env(safe-area-inset-left), 0px);
padding-right: max(env(safe-area-inset-right), 0px);
padding-bottom: calc(max(env(safe-area-inset-bottom), 0px) + clamp(1.25rem, 3vh, 2.5rem));
row-gap: clamp(2rem, 6vh, 4rem);
}
@@ -505,6 +505,14 @@ html {
}
}
/* iOS-specific padding for hero content */
@supports (-webkit-touch-callout: none) {
.hero-content {
/* Use larger fallback for Dynamic Island devices */
padding-top: calc(max(constant(safe-area-inset-top), env(safe-area-inset-top), 59px) + clamp(2rem, 8vh, 5rem));
}
}
.hero-main {
width: min(720px, 100%);
margin: 0 auto;

View File

@@ -172,32 +172,59 @@ onMounted(() => {
</script>
<style scoped>
/* Hero video container - proper safe area implementation */
/* Hero video container - Safari-compatible safe area implementation */
.hero-video-container {
position: fixed;
left: 0;
right: 0;
width: 100vw;
/* Pull up by safe-area to extend under notch */
top: calc(-1 * env(safe-area-inset-top));
/* Grow height to compensate */
height: calc(100lvh + env(safe-area-inset-top));
/* Default fallback for devices without notch */
top: 0;
height: 100vh;
z-index: 1;
pointer-events: none;
background: #ffffff;
}
/* Fallback for browsers without lvh */
@supports not (height: 100lvh) {
/* Use @supports with max() as recommended by Apple for safe area */
@supports(padding: max(0px)) {
.hero-video-container {
height: calc(100vh + env(safe-area-inset-top));
/* Use max() to provide fallback when env() returns 0 (Safari bug) */
top: calc(-1 * max(env(safe-area-inset-top), 20px));
height: calc(100vh + max(env(safe-area-inset-top), 20px));
}
}
/* Additional support for browsers with dvh */
/* 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));
}
}
}
/* Support for dynamic viewport height */
@supports (height: 100dvh) {
.hero-video-container {
height: calc(100dvh + env(safe-area-inset-top));
height: calc(100dvh + max(env(safe-area-inset-top), 59px));
}
}
/* When in standalone PWA mode, env() typically works better */
@media screen and (display-mode: standalone) {
.hero-video-container {
top: calc(-1 * env(safe-area-inset-top));
height: calc(100vh + env(safe-area-inset-top));
}
}

View File

@@ -0,0 +1,267 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<title>Safe Area Fix Test - Harbor Smith</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
height: 100%;
}
body {
margin: 0;
background: #000;
height: 100%;
}
/* Use @supports as recommended by Apple */
@supports(padding: max(0px)) {
.bg {
position: fixed;
left: 0;
right: 0;
width: 100%;
/* Use max() function to ensure fallback */
top: calc(-1 * max(env(safe-area-inset-top), 20px));
/* Safari-specific: use constant as fallback */
top: calc(-1 * max(constant(safe-area-inset-top), env(safe-area-inset-top), 20px));
height: calc(100vh + max(env(safe-area-inset-top), 20px));
height: calc(100vh + max(constant(safe-area-inset-top), env(safe-area-inset-top), 20px));
z-index: -1;
}
}
/* Fallback for browsers without @supports */
.bg {
position: fixed;
left: 0;
right: 0;
top: -44px; /* Standard notch height fallback */
width: 100%;
height: calc(100vh + 44px);
z-index: -1;
}
/* iOS-specific using -webkit-touch-callout */
@supports (-webkit-touch-callout: none) {
.bg {
/* For iPhone 14/15 Pro with Dynamic Island (59px) */
top: -59px;
height: calc(100vh + 59px);
}
}
/* Gradient background */
.bg::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(180deg,
#ff0000 0%, /* Red at very top */
#ff6600 10%, /* Orange */
#ffcc00 20%, /* Yellow */
#33cc33 30%, /* Green */
#0099ff 50%, /* Blue */
#6633cc 70%, /* Purple */
#cc33cc 90%, /* Magenta */
#000000 100% /* Black at bottom */
);
}
/* Content with proper safe area padding using max() */
@supports(padding: max(0px)) {
.content {
position: relative;
min-height: 100vh;
padding-top: max(env(safe-area-inset-top), 44px);
padding-top: max(constant(safe-area-inset-top), env(safe-area-inset-top), 44px);
padding-left: max(env(safe-area-inset-left), 0px);
padding-right: max(env(safe-area-inset-right), 0px);
padding-bottom: max(env(safe-area-inset-bottom), 0px);
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
}
/* Fallback content padding */
.content {
position: relative;
min-height: 100vh;
padding-top: 44px;
padding-left: 0;
padding-right: 0;
padding-bottom: 0;
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
@supports (-webkit-touch-callout: none) {
.content {
padding-top: 59px; /* Dynamic Island */
}
}
h1 {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 2rem;
text-align: center;
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
margin-bottom: 20px;
}
.info {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
padding: 20px;
border-radius: 10px;
margin: 20px;
max-width: 90%;
}
.info p {
margin: 10px 0;
font-family: monospace;
font-size: 14px;
}
.success {
color: #4CAF50;
}
.warning {
color: #FFC107;
}
.error {
color: #F44336;
}
/* Force fullscreen for better safe area support */
@media screen and (display-mode: standalone) {
.bg {
top: calc(-1 * env(safe-area-inset-top));
height: calc(100vh + env(safe-area-inset-top));
}
.content {
padding-top: env(safe-area-inset-top);
}
}
</style>
</head>
<body>
<div class="bg"></div>
<div class="content">
<h1>Safe Area Fix Test v2</h1>
<div class="info">
<p class="success">✅ Using hardcoded fallback: -59px for Dynamic Island</p>
<p class="success">✅ Using max() function for reliable fallbacks</p>
<p class="success">✅ Supporting both env() and constant()</p>
<p class="warning">⚠️ Safari bug: env() returns 0 in portrait mode</p>
<p style="margin-top: 20px; font-weight: bold;">Visual Test:</p>
<p>🔴 Red should be visible in the notch area</p>
<p>📝 This text should be below the notch</p>
<p style="margin-top: 20px; font-weight: bold;">Debug Values:</p>
<p>Safe Area Top: <span id="safeTop">checking...</span></p>
<p>Computed Top Padding: <span id="computedPadding">checking...</span></p>
<p>Viewport Height: <span id="viewHeight">checking...</span></p>
<p>User Agent: <span id="userAgent">checking...</span></p>
<p style="margin-top: 20px; font-weight: bold;">Workaround Status:</p>
<p id="workaround" class="warning">Using hardcoded fallback values</p>
</div>
<div class="info" style="margin-top: 20px;">
<p style="font-weight: bold;">Try These Tests:</p>
<p>1. Rotate to landscape and back to portrait</p>
<p>2. Add to Home Screen for PWA mode</p>
<p>3. Check if red gradient extends to top</p>
</div>
</div>
<script>
function detectDevice() {
const ua = navigator.userAgent;
const isIPhone = /iPhone/.test(ua);
const isIPad = /iPad/.test(ua);
const isIOS = isIPhone || isIPad;
// Detect specific iPhone models with notch/Dynamic Island
const hasNotch = isIPhone && screen.height >= 812; // iPhone X and later
const hasDynamicIsland = isIPhone && screen.height >= 852; // iPhone 14 Pro and later
return {
isIOS,
isIPhone,
hasNotch,
hasDynamicIsland,
screenHeight: screen.height,
screenWidth: screen.width
};
}
function updateDebugInfo() {
const device = detectDevice();
const styles = getComputedStyle(document.documentElement);
// Try to get safe area values
let safeTop = styles.getPropertyValue('padding-top') || '0px';
// Get computed padding on content
const content = document.querySelector('.content');
const computedPadding = window.getComputedStyle(content).paddingTop;
document.getElementById('safeTop').textContent = safeTop || '0px (Safari bug)';
document.getElementById('computedPadding').textContent = computedPadding;
document.getElementById('viewHeight').textContent = window.innerHeight + 'px';
document.getElementById('userAgent').textContent = device.isIPhone ?
(device.hasDynamicIsland ? 'iPhone 14/15 Pro' :
device.hasNotch ? 'iPhone with Notch' : 'iPhone') :
'Not iPhone';
// Update workaround status
const workaroundEl = document.getElementById('workaround');
if (parseInt(safeTop) > 0) {
workaroundEl.textContent = '✅ Safe area is working!';
workaroundEl.className = 'success';
} else if (device.hasDynamicIsland) {
workaroundEl.textContent = '⚠️ Using 59px fallback for Dynamic Island';
workaroundEl.className = 'warning';
} else if (device.hasNotch) {
workaroundEl.textContent = '⚠️ Using 44px fallback for notch';
workaroundEl.className = 'warning';
}
}
// Update on load and orientation change
window.addEventListener('load', updateDebugInfo);
window.addEventListener('orientationchange', () => {
setTimeout(updateDebugInfo, 100);
});
window.addEventListener('resize', updateDebugInfo);
// Check for standalone mode
if (window.navigator.standalone) {
document.body.classList.add('standalone');
}
</script>
</body>
</html>