/** * Static Device Detection Utility * Provides non-reactive device detection for Safari iOS reload loop prevention * Uses direct navigator.userAgent analysis without creating Vue reactive dependencies */ export interface DeviceInfo { isMobile: boolean; isIos: boolean; isSafari: boolean; isMobileSafari: boolean; isAndroid: boolean; userAgent: string; } let cachedDeviceInfo: DeviceInfo | null = null; /** * Get static device information without creating reactive dependencies * Results are cached to prevent multiple userAgent parsing */ export function getStaticDeviceInfo(): DeviceInfo { // Return cached result if available if (cachedDeviceInfo) { return cachedDeviceInfo; } // Only run on client-side if (typeof window === 'undefined' || typeof navigator === 'undefined') { cachedDeviceInfo = { isMobile: false, isIos: false, isSafari: false, isMobileSafari: false, isAndroid: false, userAgent: '' }; return cachedDeviceInfo; } const userAgent = navigator.userAgent; // Device detection logic const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent); const isIos = /iPad|iPhone|iPod/.test(userAgent) && !(window as any).MSStream; const isAndroid = /Android/i.test(userAgent); const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent); const isMobileSafari = isIos && isSafari; // Cache the result cachedDeviceInfo = { isMobile, isIos, isSafari, isMobileSafari, isAndroid, userAgent }; return cachedDeviceInfo; } /** * Get CSS classes for device-specific styling * Returns a space-separated string of CSS classes */ export function getDeviceCssClasses(baseClass: string = ''): string { const device = getStaticDeviceInfo(); const classes = [baseClass].filter(Boolean); if (device.isMobile) classes.push('is-mobile'); if (device.isIos) classes.push('is-ios'); if (device.isSafari) classes.push('is-safari'); if (device.isMobileSafari) classes.push('is-mobile-safari'); if (device.isAndroid) classes.push('is-android'); return classes.join(' '); } /** * Check if current device is mobile Safari specifically * This is the primary problematic browser for reload loops */ export function isMobileSafari(): boolean { return getStaticDeviceInfo().isMobileSafari; } /** * Apply mobile Safari specific optimizations to DOM element * Should be called once per component to prevent reactive updates */ export function applyMobileSafariOptimizations(element?: HTMLElement): void { if (!isMobileSafari()) return; const targetElement = element || document.documentElement; // Apply performance optimization classes targetElement.classList.add('is-mobile-safari', 'performance-optimized'); // Set viewport height CSS variable for mobile Safari const vh = window.innerHeight * 0.01; targetElement.style.setProperty('--vh', `${vh}px`); // Disable problematic CSS features for performance targetElement.style.setProperty('--backdrop-filter', 'none'); targetElement.style.setProperty('--will-change', 'auto'); } /** * Get viewport meta content optimized for mobile Safari */ export function getMobileSafariViewportMeta(): string { const device = getStaticDeviceInfo(); if (device.isMobileSafari) { // Prevent zoom on input focus for iOS Safari return 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'; } return 'width=device-width, initial-scale=1.0'; } /** * Clear cached device info (useful for testing) */ export function clearDeviceInfoCache(): void { cachedDeviceInfo = null; }