1(function () { 2 const STORAGE_KEY = 'insights-market-pwa-dismissed'; 3 let deferredPrompt = null; 4 let installContainer = null; 5 let installButton = null; 6 let iosHintVisible = false; 7 8 function isIos() { 9 const ua = window.navigator.userAgent || ''; 10 return /iphone|ipad|ipod/i.test(ua); 11 } 12 13 function isStandalone() { 14 return window.matchMedia('(display-mode: standalone)').matches || window.navigator.standalone === true; 15 } 16 17 function isDismissed() { 18 try { 19 return window.localStorage.getItem(STORAGE_KEY) === '1'; 20 } catch (error) { 21 return false; 22 } 23 } 24 25 function setDismissed() { 26 try { 27 window.localStorage.setItem(STORAGE_KEY, '1'); 28 } catch (error) { 29 // Ignore storage issues. 30 } 31 } 32 33 function showContainer() { 34 if (installContainer) { 35 installContainer.classList.remove('hidden'); 36 } 37 } 38 39 function hideContainer() { 40 if (installContainer) { 41 installContainer.classList.add('hidden'); 42 } 43 } 44 45 function updateButtonLabel(label) { 46 if (installButton && label) { 47 installButton.textContent = label; 48 } 49 } 50 51 function showToast(message) { 52 const toast = document.createElement('div'); 53 toast.setAttribute('role', 'status'); 54 toast.style.position = 'fixed'; 55 toast.style.left = '50%'; 56 toast.style.bottom = '24px'; 57 toast.style.transform = 'translateX(-50%)'; 58 toast.style.zIndex = '9999'; 59 toast.style.maxWidth = 'min(92vw, 360px)'; 60 toast.style.padding = '12px 16px'; 61 toast.style.borderRadius = '9999px'; 62 toast.style.background = 'rgba(15, 23, 42, 0.96)'; 63 toast.style.color = '#fff'; 64 toast.style.fontSize = '14px'; 65 toast.style.lineHeight = '1.4'; 66 toast.style.boxShadow = '0 12px 28px rgba(15, 23, 42, 0.28)'; 67 toast.textContent = message; 68 console.log(message) 69 document.body.appendChild(toast); 70 window.setTimeout(() => { 71 toast.style.opacity = '0'; 72 toast.style.transition = 'opacity 180ms ease'; 73 window.setTimeout(() => toast.remove(), 220); 74 }, 3200); 75 } 76 77 function showIosHint() { 78 if (iosHintVisible) { 79 return; 80 } 81 iosHintVisible = true; 82 showToast('Tap Share, then choose Add to Home Screen to install this app.'); 83 } 84 85 async function registerServiceWorker() { 86 const isLocal = 87 location.hostname === 'localhost' || 88 location.hostname === '127.0.0.1'; 89 90 if (!('serviceWorker' in navigator) && !isLocal) { 91 return; 92 } 93 94 try { 95 await navigator.serviceWorker.register('/sw.js', { scope: '/' }); 96 } catch (error) { 97 console.warn('Service worker registration failed:', error); 98 } 99 } 100 101 async function handleInstallClick() { 102 if (deferredPrompt) { 103 deferredPrompt.prompt(); 104 const choice = await deferredPrompt.userChoice; 105 deferredPrompt = null; 106 107 if (choice && choice.outcome === 'accepted') { 108 setDismissed(); 109 hideContainer(); 110 } 111 return; 112 } 113 114 if (isIos()) { 115 showIosHint(); 116 return; 117 } 118 119 showToast('Use your browser menu to install the app on this device.'); 120 } 121 122 function initInstallUi() { 123 installContainer = document.getElementById('pwaInstallContainer'); 124 installButton = document.getElementById('pwaInstallButton'); 125 126 if (!installContainer || !installButton || isStandalone() || isDismissed()) { 127 hideContainer(); 128 return; 129 } 130 131 if (isIos()) { 132 updateButtonLabel('Add to Home Screen'); 133 showContainer(); 134 } 135 136 installButton.addEventListener('click', handleInstallClick); 137 } 138 139 window.addEventListener('beforeinstallprompt', function (event) { 140 event.preventDefault(); 141 deferredPrompt = event; 142 143 if (!installContainer || !installButton || isStandalone() || isDismissed()) { 144 return; 145 } 146 147 updateButtonLabel('Install App'); 148 showContainer(); 149 }); 150 151 window.addEventListener('appinstalled', function () { 152 deferredPrompt = null; 153 setDismissed(); 154 hideContainer(); 155 }); 156 157 window.addEventListener('DOMContentLoaded', function () { 158 initInstallUi(); 159 registerServiceWorker(); 160 }); 161})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.