1const A2HS = { 2 deferredPrompt: null, 3 dismissedKey: 'a2hs_dismissed', 4 installedKey: 'a2hs_installed', 5 6 config: { 7 promptDelay: 3000, 8 dismissDays: 7, 9 minVisits: 1 10 }, 11 12 init(options = {}) { 13 Object.assign(this.config, options); 14 15 const visits = parseInt(localStorage.getItem('a2hs_visits') || '0') + 1; 16 localStorage.setItem('a2hs_visits', visits); 17 18 if (this.isInstalled() || this.isStandalone()) return; 19 20 this.registerSW(); 21 22 window.addEventListener('beforeinstallprompt', (e) => { 23 e.preventDefault(); 24 this.deferredPrompt = e; 25 this.schedulePrompt(); 26 }); 27 28 if (this.isIOS() && !this.isStandalone()) { 29 this.schedulePrompt(); 30 } 31 32 window.addEventListener('appinstalled', () => { 33 localStorage.setItem(this.installedKey, 'true'); 34 this.hidePrompt(); 35 }); 36 }, 37 38 registerSW() { 39 if ('serviceWorker' in navigator) { 40 navigator.serviceWorker.register('/sw.js').catch(() => {}); 41 } 42 }, 43 44 isIOS() { 45 return /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; 46 }, 47 48 isStandalone() { 49 return window.matchMedia('(display-mode: standalone)').matches || 50 window.navigator.standalone === true; 51 }, 52 53 isInstalled() { 54 return localStorage.getItem(this.installedKey) === 'true'; 55 }, 56 57 wasDismissed() { 58 const dismissed = localStorage.getItem(this.dismissedKey
58); 59 if (!dismissed) return false; 60 const daysSince = (Date.now() - parseInt(dismissed)) / (1000 * 60 * 60 * 24); 61 return daysSince < this.config.dismissDays; 62 }, 63 64 schedulePrompt() { 65 const visits = parseInt(localStorage.getItem('a2hs_visits') || '0'); 66 if (this.wasDismissed() || visits < this.config.minVisits) return; 67 setTimeout(() => this.showPrompt(), this.config.promptDelay); 68 }, 69 70 showPrompt() { 71 if (document.querySelector('.a2hs-banner')) return; 72 73 const isIOS = this.isIOS(); 74 const banner = document.createElement('div'); 75 banner.className = 'a2hs-banner'; 76 banner.innerHTML = ` 77 <div class="a2hs-content"> 78 <div class="a2hs-icon"><i class="bi bi-phone"></i></div> 79 <div class="a2hs-text"> 80 <strong>Add to Home Screen</strong> 81 <span>Quick access to order anytime</span> 82 </div> 83 <div class="a2hs-actions"> 84 ${isIOS ? 85 `<button class="a2hs-btn a2hs-btn-primary" data-action="instructions">How to Install</button>` : 86 `<button class="a2hs-btn a2hs-btn-primary" data-action="install">Install</button>` 87 } 88 <button class="a2hs-btn a2hs-btn-dismiss" data-action="dismiss"><i class="bi bi-x-lg"></i></button> 89 </div> 90 </div> 91 `; 92 93 banner.addEventListener('click', (e) => { 94 const action = e.target.closest('[data-action]')?.dataset.action; 95 if (action === 'install') this.triggerInstall(); 96 if (action === 'instructions') this.showIOSInstructions(); 97 if (action === 'dismiss') this.dismiss(); 98 }); 99 100 document.body.appendChild(banner); 101 requestAnimationFrame(() => banner.classList.add('a2hs-visible')); 102 }, 103 104 hidePrompt() { 105 const banner = document.querySelector('.a2hs-banner'); 106 if (banner) { 107 banner.classList.remove('a2hs-visible'); 108 setTimeout(() => banner.remove(), 300); 109 } 110 }, 111 112 dismiss() { 113 localStorage.setItem(this.dismissedKey, Date.now().toString()); 114 this.hidePrompt(); 115 }, 116 117 async triggerInstall() { 118 if (!this.deferredPrompt) return; 119 this.deferredPrompt.prompt(); 120 const { outcome } = await this.deferredPrompt.userChoice; 121 if (outcome === 'accepted') localStorage.setItem(this.installedKey, 'true'); 122 this.deferredPrompt = null; 123 this.hidePrompt(); 124 }, 125 126 showIOSInstructions() { 127 const modal = document.createElement('div'); 128 modal.className = 'a2hs-modal'; 129 modal.innerHTML = ` 130 <div class="a2hs-modal-content"> 131 <button class="a2hs-modal-close" data-action="close"><i class="bi bi-x-lg"></i></button> 132 <h3>Install on iPhone/iPad</h3> 133 <ol class="a2hs-steps"> 134 <li><i class="bi bi-box-arrow-up"></i><span>Tap the <strong>Share</strong> button in Safari</span></li> 135 <li><i class="bi bi-plus-square"></i><span>Scroll and tap <strong>Add to Home Screen</strong></span></li> 136 <li><i class="bi bi-check-lg"></i><span>Tap <strong>Add</strong> to confirm</span></li> 137 </ol> 138 <button class="a2hs-btn a2hs-btn-primary a2hs-btn-block" data-action="close">Got it</button> 139 </div> 140 `; 141 142 modal.addEventListener('click', (e) => { 143 if (e.target === modal || e.target.closest('[data-action="close"]')) { 144 modal.classList.remove('a2hs-visible'); 145 setTimeout(() => modal.remove(), 300); 146 } 147 }); 148 149 document.body.appendChild(modal); 150 requestAnimationFrame(() => modal.classList.add('a2hs-visible')); 151 this.hidePrompt(); 152 } 153}; 154 155document.addEventListener('DOMContentLoaded', () => A2HS.init(window.A2HS_CONFIG || {}));
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.