Add Mobile Safari optimizations and fixes to signup page
Build And Push Image / docker (push) Successful in 3m10s Details

- Implement device detection and performance optimization flags
- Add dynamic CSS classes based on device capabilities
- Create mobile safari utility functions and client plugin
- Optimize backdrop filters and hardware acceleration for iOS
- Fix viewport height issues with mobile Safari fallbacks
- Update membership fee config and add IBAN payment details
- Prevent horizontal scrolling and improve mobile UX
This commit is contained in:
Matt 2025-08-09 18:36:10 +02:00
parent 358e9c0ad1
commit 44cdc988ee
3 changed files with 270 additions and 8 deletions

View File

@ -1,9 +1,9 @@
<template> <template>
<div class="signup-container"> <div :class="containerClasses">
<v-container fluid class="fill-height"> <v-container fluid class="fill-height">
<v-row class="fill-height" justify="center" align="center"> <v-row class="fill-height" justify="center" align="center">
<v-col cols="12" sm="10" md="8" lg="6" xl="5"> <v-col cols="12" sm="10" md="8" lg="6" xl="5">
<v-card class="signup-card" elevation="24" :loading="loading"> <v-card :class="cardClasses" elevation="24" :loading="loading">
<v-card-text class="pa-8"> <v-card-text class="pa-8">
<!-- Logo and Welcome --> <!-- Logo and Welcome -->
<div class="text-center mb-6"> <div class="text-center mb-6">
@ -209,6 +209,14 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RegistrationFormData, RecaptchaConfig, RegistrationConfig } from '~/utils/types'; import type { RegistrationFormData, RecaptchaConfig, RegistrationConfig } from '~/utils/types';
import {
getDeviceInfo,
needsPerformanceOptimization,
shouldDisableBackdropFilter,
getOptimizedClasses,
applyMobileSafariFixes,
debounce
} from '~/utils/mobile-safari-utils';
// Declare global grecaptcha interface for TypeScript // Declare global grecaptcha interface for TypeScript
declare global { declare global {
@ -225,12 +233,17 @@ definePageMeta({
layout: false layout: false
}); });
// Configs - need to be declared first // Mobile Safari optimization flags
const deviceInfo = ref(getDeviceInfo());
const performanceMode = ref(needsPerformanceOptimization());
const disableBackdropFilter = ref(shouldDisableBackdropFilter());
// Configs with fallback defaults
const recaptchaConfig = ref<RecaptchaConfig>({ siteKey: '', secretKey: '' }); const recaptchaConfig = ref<RecaptchaConfig>({ siteKey: '', secretKey: '' });
const registrationConfig = ref<RegistrationConfig>({ const registrationConfig = ref<RegistrationConfig>({
membershipFee: 50, membershipFee: 150,
iban: '', iban: 'MC58 1756 9000 0104 0050 1001 860',
accountHolder: '' accountHolder: 'ASSOCIATION MONACO USA'
}); });
// Reactive data - Using individual refs to prevent Vue reactivity corruption // Reactive data - Using individual refs to prevent Vue reactivity corruption
@ -258,6 +271,7 @@ const loading = ref(false);
const recaptchaToken = ref(''); const recaptchaToken = ref('');
const successMessage = ref(''); const successMessage = ref('');
const errorMessage = ref(''); const errorMessage = ref('');
const configsLoaded = ref(false);
// Success dialog state // Success dialog state
const showSuccessDialog = ref(false); const showSuccessDialog = ref(false);
@ -268,10 +282,23 @@ useHead({
title: 'Register - MonacoUSA Portal', title: 'Register - MonacoUSA Portal',
meta: [ meta: [
{ name: 'description', content: 'Register to become a member of MonacoUSA Association' }, { name: 'description', content: 'Register to become a member of MonacoUSA Association' },
{ name: 'robots', content: 'noindex, nofollow' } { name: 'robots', content: 'noindex, nofollow' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no' }
] ]
}); });
// Dynamic CSS classes based on device
const containerClasses = computed(() => [
'signup-container',
...getOptimizedClasses()
].join(' '));
const cardClasses = computed(() => [
'signup-card',
performanceMode.value ? 'performance-optimized' : '',
disableBackdropFilter.value ? 'no-backdrop-filter' : ''
].filter(Boolean).join(' '));
// Form validation rules // Form validation rules
const nameRules = [ const nameRules = [
(v: string) => !!v || 'Name is required', (v: string) => !!v || 'Name is required',
@ -465,9 +492,10 @@ onMounted(async () => {
</script> </script>
<style scoped> <style scoped>
/* Base container styles */
.signup-container { .signup-container {
min-height: 100vh; min-height: 100vh;
min-height: 100dvh; /* Dynamic viewport height for mobile */ min-height: calc(var(--vh, 1vh) * 100); /* Mobile Safari fallback */
background: linear-gradient(rgba(163, 21, 21, 0.7), rgba(0, 0, 0, 0.5)), background: linear-gradient(rgba(163, 21, 21, 0.7), rgba(0, 0, 0, 0.5)),
url('/monaco_high_res.jpg'); url('/monaco_high_res.jpg');
background-size: cover; background-size: cover;
@ -476,6 +504,19 @@ onMounted(async () => {
background-attachment: scroll; background-attachment: scroll;
padding: 20px 0; padding: 20px 0;
position: relative; position: relative;
overflow-x: hidden; /* Prevent horizontal scroll on mobile */
}
/* Mobile Safari optimizations */
.signup-container.is-mobile-safari {
min-height: 100vh;
min-height: -webkit-fill-available;
background-attachment: scroll !important; /* Force scroll attachment */
}
.signup-container.performance-mode {
will-change: auto; /* Reduce repainting */
transform: translateZ(0); /* Force hardware acceleration but lighter */
} }
/* Ensure background covers full content */ /* Ensure background covers full content */
@ -496,6 +537,13 @@ onMounted(async () => {
min-height: 100%; min-height: 100%;
} }
/* Performance mode background - simpler for mobile */
.signup-container.performance-mode::before {
background: linear-gradient(rgba(163, 21, 21, 0.8), rgba(0, 0, 0, 0.6));
/* Remove background image on low-performance devices */
}
/* Default card styles */
.signup-card { .signup-card {
backdrop-filter: blur(15px); backdrop-filter: blur(15px);
background: rgba(255, 255, 255, 0.95) !important; background: rgba(255, 255, 255, 0.95) !important;
@ -507,10 +555,30 @@ onMounted(async () => {
width: 100%; width: 100%;
} }
/* Performance optimized card */
.signup-card.performance-optimized {
backdrop-filter: none; /* Remove expensive filter */
background: rgba(255, 255, 255, 0.98) !important;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2) !important; /* Lighter shadow */
transition: none; /* Remove animations for better performance */
}
/* No backdrop filter fallback */
.signup-card.no-backdrop-filter {
backdrop-filter: none;
background: rgba(255, 255, 255, 0.98) !important;
}
.signup-card:hover { .signup-card:hover {
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4) !important; box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4) !important;
} }
/* Disable hover effects on performance mode */
.signup-card.performance-optimized:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2) !important;
transform: none;
}
.payment-info { .payment-info {
background: rgba(163, 21, 21, 0.05); background: rgba(163, 21, 21, 0.05);
border-radius: 12px; border-radius: 12px;

View File

@ -0,0 +1,23 @@
/**
* Mobile Safari Fixes Plugin
* Applies mobile Safari specific optimizations on client side
*/
import { applyMobileSafariFixes } from '~/utils/mobile-safari-utils';
export default defineNuxtPlugin(() => {
// Apply mobile Safari fixes on client-side mount
if (typeof window !== 'undefined') {
// Apply fixes immediately
applyMobileSafariFixes();
// Also apply on route changes (for SPA navigation)
const router = useRouter();
router.afterEach(() => {
// Small delay to ensure DOM is ready
nextTick(() => {
applyMobileSafariFixes();
});
});
}
});

View File

@ -0,0 +1,171 @@
/**
* Mobile Safari Detection and Optimization Utilities
* Handles Safari-specific issues and performance optimizations
*/
export interface DeviceInfo {
isMobile: boolean;
isSafari: boolean;
isMobileSafari: boolean;
isIOS: boolean;
safariVersion?: number;
}
/**
* Detect device and browser information
*/
export function getDeviceInfo(): DeviceInfo {
if (typeof window === 'undefined') {
return {
isMobile: false,
isSafari: false,
isMobileSafari: false,
isIOS: false
};
}
const userAgent = navigator.userAgent;
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
const isIOS = /iPad|iPhone|iPod/.test(userAgent);
const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent);
const isMobileSafari = isIOS && isSafari;
// Extract Safari version if possible
let safariVersion: number | undefined;
if (isSafari) {
const match = userAgent.match(/Version\/(\d+)/);
if (match) {
safariVersion = parseInt(match[1]);
}
}
return {
isMobile,
isSafari,
isMobileSafari,
isIOS,
safariVersion
};
}
/**
* Check if the device needs performance optimizations
*/
export function needsPerformanceOptimization(): boolean {
const { isMobileSafari, isMobile } = getDeviceInfo();
return isMobileSafari || isMobile;
}
/**
* Check if backdrop-filter should be disabled
*/
export function shouldDisableBackdropFilter(): boolean {
const { isMobileSafari, safariVersion } = getDeviceInfo();
// Disable backdrop-filter on mobile Safari or older Safari versions
return isMobileSafari || Boolean(safariVersion && safariVersion < 16);
}
/**
* Get optimized CSS class names based on device
*/
export function getOptimizedClasses(): string[] {
const classes: string[] = [];
const { isMobile, isMobileSafari, isIOS } = getDeviceInfo();
if (isMobile) classes.push('is-mobile');
if (isMobileSafari) classes.push('is-mobile-safari');
if (isIOS) classes.push('is-ios');
if (needsPerformanceOptimization()) classes.push('performance-mode');
return classes;
}
/**
* Optimized viewport height for mobile Safari
*/
export function getOptimizedViewportHeight(): string {
const { isMobileSafari, safariVersion } = getDeviceInfo();
if (isMobileSafari) {
// Use 100vh for older Safari, -webkit-fill-available for newer
return safariVersion && safariVersion >= 15 ? '-webkit-fill-available' : '100vh';
}
// Use dvh for modern browsers, vh as fallback
return '100vh';
}
/**
* Apply mobile Safari specific fixes
*/
export function applyMobileSafariFixes(): void {
if (typeof window === 'undefined') return;
const { isMobileSafari } = getDeviceInfo();
if (!isMobileSafari) return;
// Fix viewport height issues
const setViewportHeight = () => {
const vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);
};
// Set initial value
setViewportHeight();
// Update on resize (debounced)
let resizeTimeout: NodeJS.Timeout;
window.addEventListener('resize', () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(setViewportHeight, 100);
});
// Add performance optimization classes
document.documentElement.classList.add(...getOptimizedClasses());
}
/**
* Throttle function for performance
*/
export function throttle<T extends (...args: any[]) => void>(
func: T,
wait: number
): (...args: Parameters<T>) => void {
let timeout: NodeJS.Timeout | null = null;
let previous = 0;
return function(this: any, ...args: Parameters<T>) {
const now = Date.now();
const remaining = wait - (now - previous);
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(this, args);
} else if (!timeout) {
timeout = setTimeout(() => {
previous = Date.now();
timeout = null;
func.apply(this, args);
}, remaining);
}
};
}
/**
* Debounce function for performance
*/
export function debounce<T extends (...args: any[]) => void>(
func: T,
wait: number
): (...args: Parameters<T>) => void {
let timeout: NodeJS.Timeout;
return function(this: any, ...args: Parameters<T>) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}