class PWAInstallPrompt { constructor() { this.deferredPrompt = null; this.isInstalled = false; this.installButton = null; this.installBanner = null; this.dismissedCount = 0; this.maxDismissals = 3; this.installationChecked = false; // Per-page-load dismissal: hide the pill until the next reload/visit // instead of locking it out for 24h, so it reliably comes back. this.userDismissed = false; this.init(); } init() { this.checkInstallationStatus(); this.setupEventListeners(); this.createInstallButton(); this.loadInstallPreferences(); if (this.isIOSSafari()) { this.startInstallationMonitoring(); } } checkInstallationStatus() { const isStandalone = window.matchMedia('(display-mode: standalone)').matches; const isIOSStandalone = window.navigator.standalone === true; const hasInstallPreference = this.loadInstallPreferences().installed; if (isStandalone || isIOSStandalone || hasInstallPreference) { this.isInstalled = true; console.log('📱 App is already installed as PWA'); document.body.classList.add('pwa-installed'); document.body.classList.remove('pwa-browser'); this.hideInstallPrompts(); if (this.isIOSSafari()) { document.body.classList.add('ios-pwa'); } this.installationChecked = true; return true; } this.isInstalled = false; document.body.classList.add('pwa-browser'); document.body.classList.remove('pwa-installed'); if (this.isIOSSafari()) { document.body.classList.add('ios-safari'); } this.installationChecked = true; return false; } startInstallationMonitoring() { let wasStandalone = window.navigator.standalone; const checkStandalone = () => { const isStandalone = window.navigator.standalone; if (isStandalone && !wasStandalone && !this.isInstalled) { this.isInstalled = true; this.hideInstallPrompts(); this.showInstallSuccess(); document.body.classList.remove('pwa-browser', 'ios-safari'); document.body.classList.add('pwa-installed', 'ios-pwa'); this.saveInstallPreference('installed', true); } wasStandalone = isStandalone; }; setInterval(checkStandalone, 2000); window.addEventListener('visibilitychange', () => { if (!document.hidden) { setTimeout(checkStandalone, 1000); } }); window.addEventListener('focus', () => { setTimeout(checkStandalone, 500); }); } setupEventListeners() { window.addEventListener('beforeinstallprompt', (event) => { // Don't prevent default - let browser show its own banner this.deferredPrompt = event; if (this.checkInstallationStatus()) { return; } if (!this.isInstalled && this.shouldShowPrompt()) { setTimeout(() => this.showInstallOptions(), 1000); } }); window.addEventListener('appinstalled', () => { this.isInstalled = true; this.hideInstallPrompts(); this.showInstallSuccess(); this.saveInstallPreference('installed', true); document.body.classList.remove('pwa-browser', 'ios-safari'); document.body.classList.add('pwa-installed'); }); window.addEventListener('visibilitychange', () => { if (document.hidden) return; setTimeout(() => { const wasInstalled = this.isInstalled; this.checkInstallationStatus(); if (!wasInstalled && this.isInstalled) { this.hideInstallPrompts(); this.showInstallSuccess(); } }, 1000); }); window.addEventListener('focus', () => { setTimeout(() => { const wasInstalled = this.isInstalled; this.checkInstallationStatus(); if (!wasInstalled && this.isInstalled) { this.hideInstallPrompts(); this.showInstallSuccess(); } }, 500); }); } createInstallButton() { if (this.isInstalled) { return; } // Compact "pill" install prompt — translated from the Claude Design // component (Install Prompt.dc.html, compact variant). Styling is inline // so it tracks the design without relying on Tailwind/global CSS. this.installButton = document.createElement('div'); this.installButton.id = 'pwa-install-button'; this.installButton.className = 'hidden'; this.installButton.style.cssText = "position:fixed; bottom:24px; right:24px; z-index:50; font-family:'Manrope',system-ui,-apple-system,sans-serif;"; this.installButton.innerHTML = `
`; const pill = this.installButton.querySelector('.install-pill'); pill.addEventListener('mouseenter', () => { pill.style.background = '#ff9637'; pill.style.transform = 'translateY(-2px)'; }); pill.addEventListener('mouseleave', () => { pill.style.background = '#f0892a'; pill.style.transform = 'none'; }); const closeBtn = this.installButton.querySelector('.close-btn'); closeBtn.addEventListener('mouseenter', () => { closeBtn.style.color = '#e5727a'; closeBtn.style.borderColor = 'rgba(229,114,122,0.4)'; closeBtn.style.background = '#201416'; }); closeBtn.addEventListener('mouseleave', () => { closeBtn.style.color = '#9a9aa2'; closeBtn.style.borderColor = 'rgba(255,255,255,0.1)'; closeBtn.style.background = '#1a1a1d'; }); this.installButton.addEventListener('click', (e) => { if (!e.target.closest('.close-btn')) { this.handleInstallClick(); } }); closeBtn.addEventListener('click', (e) => { e.stopPropagation(); this.dismissInstallPrompt(); }); document.body.appendChild(this.installButton); } createInstallBanner() { if (this.isInstalled || this.installBanner) { return; } this.installBanner = document.createElement('div'); this.installBanner.id = 'pwa-install-banner'; this.installBanner.className = 'pwa-install-banner fixed bottom-0 left-0 right-0 transform translate-y-full transition-transform duration-300 z-40'; this.installBanner.innerHTML = `
Install SecureBit.chat
Get the native app experience with enhanced security
`; // Handle banner actions this.installBanner.addEventListener('click', (event) => { const action = event.target.closest('[data-action]')?.dataset.action; if (action === 'install') { this.handleInstallClick(); } else if (action === 'close') { this.dismissInstallPrompt(); } }); document.body.appendChild(this.installBanner); } showInstallOptions() { if (this.isIOSSafari()) { this.showInstallButton(); } else if (this.isMobileDevice()) { this.showInstallBanner(); } else { this.showInstallButton(); } } showInstallButton() { if (this.installButton && !this.isInstalled) { this.installButton.classList.remove('hidden'); // Add entrance animation setTimeout(() => { this.installButton.style.transform = 'scale(1.1)'; setTimeout(() => { this.installButton.style.transform = 'scale(1)'; }, 200); }, 100); } else { } } showInstallBanner() { if (this.checkInstallationStatus()) { return; } if (!this.installBanner) { this.createInstallBanner(); } if (this.installBanner && !this.isInstalled) { setTimeout(() => { this.installBanner.classList.add('show'); this.installBanner.style.transform = 'translateY(0)'; }, 1000); } else { } } hideInstallPrompts() { if (this.installButton) { this.installButton.classList.add('hidden'); if (this.isInstalled) { this.installButton.remove(); this.installButton = null; } console.log('💿 Install button hidden'); } if (this.installBanner) { this.installBanner.classList.remove('show'); this.installBanner.style.transform = 'translateY(100%)'; if (this.isInstalled) { setTimeout(() => { if (this.installBanner) { this.installBanner.remove(); this.installBanner = null; } }, 300); } } } async handleInstallClick() { if (this.isIOSSafari()) { this.showIOSInstallInstructions(); return; } if (!this.deferredPrompt) { console.warn('⚠️ Install prompt not available'); this.showFallbackInstructions(); return; } try { const result = await this.deferredPrompt.prompt(); if (result.outcome === 'accepted') { this.isInstalled = true; this.hideInstallPrompts(); this.saveInstallPreference('accepted', true); this.saveInstallPreference('installed', true); } else { this.handleInstallDismissal(); } this.deferredPrompt = null; } catch (error) { console.error('❌ Install prompt failed:', error); this.showFallbackInstructions(); } } showIOSInstallInstructions() { const modal = document.createElement('div'); modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4 backdrop-blur-sm'; modal.innerHTML = `

Install on iOS

1
Tap the Share button
Usually at the bottom of Safari
2
Find "Add to Home Screen"
Scroll down in the share menu
3
Tap "Add"
Confirm to install SecureBit.chat

After installation, open SecureBit from your home screen for the best experience.

`; const gotItBtn = modal.querySelector('.got-it-btn'); const closeBtn = modal.querySelector('.close-btn'); gotItBtn.addEventListener('click', () => { modal.remove(); this.saveInstallPreference('ios_instructions_shown', Date.now()); }); closeBtn.addEventListener('click', () => { modal.remove(); this.dismissedCount++; this.saveInstallPreference('dismissed', this.dismissedCount); }); document.body.appendChild(modal); this.saveInstallPreference('ios_instructions_shown', Date.now()); } showFallbackInstructions() { // Per-browser install guide — translated from the Claude Design component // (Install Guide.dc.html). Styling is inline so it tracks the design. const modal = document.createElement('div'); modal.id = 'pwa-install-guide'; modal.style.cssText = "position:fixed; inset:0; z-index:9999; display:flex; align-items:center; justify-content:center; padding:24px; background:rgba(8,8,10,0.55); backdrop-filter:blur(3px); -webkit-backdrop-filter:blur(3px); animation:igFade .3s ease; font-family:'Manrope',system-ui,-apple-system,sans-serif;"; const rowIcon = { chromeEdge: '', firefox: '', safari: '' }; const row = (icon, title, desc, delay, nowrap) => `
${icon}
${title}
${desc}
`; modal.innerHTML = `

Install SecureBit

Your browser handles installs its own way. Pick the steps that match yours.

${row(rowIcon.chromeEdge, 'Chrome / Edge', 'Click the install icon in the address bar', '.34s', false)} ${row(rowIcon.firefox, 'Firefox', 'Add a bookmark to your home screen', '.42s', false)} ${row(rowIcon.safari, 'Safari', 'Share → Add to Home Screen', '.5s', true)}
`; const closeX = modal.querySelector('.close-x'); closeX.addEventListener('mouseenter', () => { closeX.style.color = '#e5727a'; closeX.style.borderColor = 'rgba(229,114,122,0.4)'; }); closeX.addEventListener('mouseleave', () => { closeX.style.color = '#8a8a92'; closeX.style.borderColor = 'rgba(255,255,255,0.08)'; }); const gotIt = modal.querySelector('.got-it'); gotIt.addEventListener('mouseenter', () => { gotIt.style.borderColor = 'rgba(255,255,255,0.22)'; gotIt.style.background = 'rgba(255,255,255,0.06)'; }); gotIt.addEventListener('mouseleave', () => { gotIt.style.borderColor = 'rgba(255,255,255,0.1)'; gotIt.style.background = 'rgba(255,255,255,0.03)'; }); const close = () => modal.remove(); closeX.addEventListener('click', close); gotIt.addEventListener('click', close); modal.addEventListener('click', (e) => { if (e.target === modal) close(); }); if (!document.getElementById('pwa-install-guide-kf')) { const style = document.createElement('style'); style.id = 'pwa-install-guide-kf'; style.textContent = '@keyframes igPop{from{opacity:0;transform:scale(.96) translateY(10px)}to{opacity:1;transform:scale(1) translateY(0)}}@keyframes igFade{from{opacity:0}to{opacity:1}}@keyframes igRow{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}'; document.head.appendChild(style); } document.body.appendChild(modal); } showInstallSuccess() { const notification = document.createElement('div'); notification.className = 'fixed top-4 right-4 bg-green-500 text-white p-4 rounded-lg shadow-lg z-50 max-w-sm transform translate-x-full transition-transform duration-300'; const successText = this.isIOSSafari() ? 'iOS App installed! Open from home screen.' : 'SecureBit.chat is now on your device'; notification.innerHTML = `
App Installed!
${successText}
`; document.body.appendChild(notification); setTimeout(() => { notification.classList.remove('translate-x-full'); }, 100); setTimeout(() => { notification.classList.add('translate-x-full'); setTimeout(() => notification.remove(), 300); }, 5000); this.hideInstallPrompts(); } shouldShowPrompt() { if (this.checkInstallationStatus()) { return false; } const preferences = this.loadInstallPreferences(); if (preferences.installed) { this.isInstalled = true; this.hideInstallPrompts(); return false; } // Hidden only for the current page load once the user dismisses it; // a reload or a fresh visit surfaces it again (until installed). if (this.userDismissed) return false; if (this.isIOSSafari()) { const lastShown = preferences.ios_instructions_shown; if (lastShown && Date.now() - lastShown < 24 * 60 * 60 * 1000) { return false; } return true; } return true; } dismissInstallPrompt() { this.userDismissed = true; this.dismissedCount++; this.hideInstallPrompts(); this.saveInstallPreference('dismissed', this.dismissedCount); } handleInstallDismissal() { this.dismissedCount++; this.saveInstallPreference('dismissed', this.dismissedCount); if (this.dismissedCount < this.maxDismissals) { setTimeout(() => { if (!this.isInstalled && this.shouldShowPrompt()) { this.showInstallButton(); } }, 300000); } } showFinalDismissalMessage() { const notification = document.createElement('div'); notification.className = 'fixed bottom-4 left-4 right-4 bg-blue-500/90 text-white p-4 rounded-lg shadow-lg z-50 backdrop-blur-sm'; notification.innerHTML = `
Install Anytime
You can still install SecureBit.chat from your browser's menu for the best experience.
`; const okBtn = notification.querySelector('.ok-btn'); okBtn.addEventListener('click', () => { notification.remove(); }); document.body.appendChild(notification); setTimeout(() => { if (notification.parentElement) { notification.remove(); } }, 10000); } saveInstallPreference(action, value) { const preferences = this.loadInstallPreferences(); preferences[action] = value; if (action === 'dismissed') { preferences.lastDismissed = Date.now(); } try { localStorage.setItem('pwa_install_prefs', JSON.stringify(preferences)); } catch (error) { console.warn('⚠️ Could not save install preferences:', error); } } loadInstallPreferences() { try { const saved = localStorage.getItem('pwa_install_prefs'); return saved ? JSON.parse(saved) : { dismissed: 0, installed: false }; } catch (error) { console.warn('⚠️ Could not load install preferences:', error); return { dismissed: 0, installed: false }; } } isMobileDevice() { return /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); } isIOSSafari() { const userAgent = navigator.userAgent; const isIOS = /iPad|iPhone|iPod/.test(userAgent); const isSafari = /Safari/.test(userAgent) && !/CriOS|FxiOS|EdgiOS/.test(userAgent); return isIOS && isSafari; } // Public API methods showInstallPrompt() { if (this.isIOSSafari()) { this.showIOSInstallInstructions(); } else if (this.deferredPrompt && !this.isInstalled) { this.handleInstallClick(); } else { this.showFallbackInstructions(); } } hideInstallPrompt() { this.hideInstallPrompts(); } getInstallStatus() { this.checkInstallationStatus(); return { isInstalled: this.isInstalled, canPrompt: !!this.deferredPrompt && !this.isInstalled, isIOSSafari: this.isIOSSafari(), dismissedCount: this.dismissedCount, shouldShowPrompt: this.shouldShowPrompt() }; } resetDismissals() { this.dismissedCount = 0; this.saveInstallPreference('dismissed', 0); } // Method for setting service worker registration setServiceWorkerRegistration(registration) { this.swRegistration = registration; } forceInstallationCheck() { this.installationChecked = false; const wasInstalled = this.isInstalled; const isNowInstalled = this.checkInstallationStatus(); if (!wasInstalled && isNowInstalled) { this.hideInstallPrompts(); this.showInstallSuccess(); } return isNowInstalled; } } // Export for module use if (typeof module !== 'undefined' && module.exports) { module.exports = PWAInstallPrompt; } else { window.PWAInstallPrompt = PWAInstallPrompt; } // Auto-initialize if (typeof window !== 'undefined') { window.addEventListener('DOMContentLoaded', () => { if (!window.pwaInstallPrompt) { window.pwaInstallPrompt = new PWAInstallPrompt(); } }); window.addEventListener('load', () => { if (window.pwaInstallPrompt) { setTimeout(() => { window.pwaInstallPrompt.forceInstallationCheck(); }, 1000); } }); }