1+ < script >
2+ // PWA Install Injector
3+ ( function ( ) {
4+ 'use strict' ;
5+ const performInjection = ( ) => {
6+ let deferredPrompt = null ;
7+ const lastDismissed = localStorage . getItem ( 'pwa_dismissed_at' ) ;
8+ const cooldown = 3 * 24 * 60 * 60 * 1000 ;
9+ const isStandalone = window . matchMedia ( '(display-mode: standalone)' ) . matches ;
110
11+ const isIOS = / i P a d | i P h o n e | i P o d / . test ( navigator . userAgent ) ||
12+ ( navigator . platform === 'MacIntel' && navigator . maxTouchPoints > 1 ) ;
13+
14+ if ( isStandalone || ( lastDismissed && ( Date . now ( ) - lastDismissed < cooldown ) ) ) {
15+ return ;
16+ }
17+
18+ window . addEventListener ( 'beforeinstallprompt' , ( e ) => {
19+ e . preventDefault ( ) ;
20+ deferredPrompt = e ;
21+ setTimeout ( injectPwaResources , 2000 ) ;
22+ } ) ;
23+
24+ if ( isIOS ) {
25+ setTimeout ( injectPwaResources , 2000 ) ;
26+ }
27+
28+ /**
29+ * Injects CSS and HTML UI components into the DOM
30+ */
31+ function injectPwaResources ( ) {
32+ if ( document . getElementById ( 'pwa-mini-banner' ) ) return ;
33+
34+ const style = document . createElement ( 'style' ) ;
35+ style . id = 'pwa-dynamic-styles' ;
36+ style . textContent = `
37+ .pwa-sticky-bar {
38+ position: fixed; bottom: 24px; left: 50%;
39+ transform: translateX(-50%) translateY(150%);
40+ width: calc(100% - 32px); max-width: 400px;
41+ z-index: 9999; opacity: 0; pointer-events: none;
42+ transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.4s;
43+ }
44+ .pwa-sticky-bar.visible { transform: translateX(-50%) translateY(0); opacity: 1; pointer-events: auto; }
45+ .pwa-bar-content {
46+ background: rgba(30, 37, 42, 0.85); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
47+ border: 1px solid var(--border); border-radius: 24px;
48+ padding: 10px 16px; display: flex; align-items: center;
49+ justify-content: space-between; box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
50+ }
51+ .pwa-bar-info { display: flex; align-items: center; gap: 12px; }
52+ .pwa-bar-icon {
53+ width: 42px; height: 42px; background: var(--accent-green);
54+ border-radius: 12px; display: flex; align-items: center;
55+ justify-content: center; font-size: 1.3rem;
56+ box-shadow: 0 4px 12px rgba(16, 185, 129, 0.2);
57+ }
58+ .pwa-bar-text strong { color: var(--text-white); font-size: 0.9rem; display: block; line-height: 1.2; }
59+ .pwa-bar-text span { color: var(--text-gray); font-size: 0.75rem; }
60+ .btn-pwa-add {
61+ background: var(--accent-green); color: #000; border: none;
62+ padding: 8px 18px; border-radius: 14px; font-weight: 800;
63+ font-size: 0.75rem; text-transform: uppercase; cursor: pointer;
64+ transition: transform 0.2s;
65+ }
66+ .btn-pwa-add:active { transform: scale(0.95); }
67+ .btn-pwa-close {
68+ background: transparent; color: var(--text-gray);
69+ border: none; font-size: 1.5rem; cursor: pointer;
70+ line-height: 1; padding: 0 4px;
71+ }
72+ ` ;
73+ document . head . appendChild ( style ) ;
74+
75+ const pwaWrapper = document . createElement ( 'div' ) ;
76+ pwaWrapper . id = 'pwa-mini-banner' ;
77+ pwaWrapper . className = 'pwa-sticky-bar' ;
78+ pwaWrapper . innerHTML = `
79+ <div class="pwa-bar-content">
80+ <div class="pwa-bar-info">
81+ <span class="pwa-bar-icon">📊</span>
82+ <div class="pwa-bar-text">
83+ <strong>QuantVAT App</strong>
84+ <span>Install for instant access</span>
85+ </div>
86+ </div>
87+ <div style="display: flex; align-items: center; gap: 8px;">
88+ <button id="btn-pwa-install" class="btn-pwa-add">${ isIOS ? 'Show' : 'Add' } </button>
89+ <button id="btn-pwa-close-trigger" class="btn-pwa-close">×</button>
90+ </div>
91+ </div>
92+ ` ;
93+ document . body . appendChild ( pwaWrapper ) ;
94+ setTimeout ( ( ) => pwaWrapper . classList . add ( 'visible' ) , 100 ) ;
95+
96+ document . getElementById ( 'btn-pwa-install' ) . addEventListener ( 'click' , async ( ) => {
97+ if ( isIOS ) {
98+ alert ( "To install: Tap the 'Share' icon and select 'Add to Home Screen'." ) ;
99+ return ;
100+ }
101+ if ( ! deferredPrompt ) return ;
102+ pwaWrapper . classList . remove ( 'visible' ) ;
103+ deferredPrompt . prompt ( ) ;
104+ await deferredPrompt . userChoice ;
105+ deferredPrompt = null ;
106+ } ) ;
107+
108+ document . getElementById ( 'btn-pwa-close-trigger' ) . addEventListener ( 'click' , ( ) => {
109+ pwaWrapper . classList . remove ( 'visible' ) ;
110+ localStorage . setItem ( 'pwa_dismissed_at' , Date . now ( ) ) ;
111+ setTimeout ( ( ) => {
112+ pwaWrapper . remove ( ) ;
113+ style . remove ( ) ;
114+ } , 600 ) ;
115+ } ) ;
116+ }
117+ } ;
118+
119+ if ( 'requestIdleCallback' in window ) {
120+ window . requestIdleCallback ( performInjection ) ;
121+ } else {
122+ window . addEventListener ( 'load' , performInjection ) ;
123+ }
124+ } ) ( ) ;
125+ </ script >
0 commit comments