PageSourceSearch

https://www.want.nl/wp-content/plugins/Mediahuis-simplePwa/assets/js/simple-pwa.js?ver=1790929178

js want.nl collected 2026-10-02 08:27:56 UTC 58,588 bytes, 1,334 lines download raw bytes

1// Simple PWA JavaScript
2// console.log('PWA: JavaScript file loaded');
3// console.log('PWA: simplePWA config:', typeof simplePWA !== 'undefined' ? simplePWA : 'NOT DEFINED');
4(function () {
5    'use strict';
6
7    let deferredPrompt = null;
8    let installButton;
9    let beforeInstallPromptFired = false;
10
11    // Listen for beforeinstallprompt IMMEDIATELY - before DOMContentLoaded
12    // This is critical because Chrome can fire this event very early
13    window.addEventListener('beforeinstallprompt', function (e) {
14        // console.log('PWA: beforeinstallprompt fired EARLY - native install available');
15        e.preventDefault();
16        deferredPrompt = e;
17        beforeInstallPromptFired = true;
18
19        // If DOM is already loaded and prompt exists, update it
20        if (document.readyState !== 'loading') {
21            const existingPrompt = document.getElementById('pwa-install-prompt');
22            if (existingPrompt && !existingPrompt.classList.contains('hidden')) {
23                updateInstallButtonForNative();
24            }
25        }
26    });
27
28    // Initialize PWA functionality when DOM is loaded
29    document.addEventListener('DOMContentLoaded', function () {
30        // console.log('PWA: DOM loaded, initializing...');
31
32        // Track PWA pageview only for standalone mode (web pageviews tracked elsewhere)
33        if (isStandalone()) {
34            // Mark as installed so the regular website won't show the install prompt
35            localStorage.setItem('pwa-installed', '1');
36
37            trackPWAEvent('Pageview', 'pwa', {
38                'pwa_display_mode': 'pwa'
39            });
40
41            // Track PWA open (once per session)
42            if (!sessionStorage.getItem('pwa-open-tracked')) {
43                sessionStorage.setItem('pwa-open-tracked', '1');
44                const platform = isAndroidDevice() ? 'android' : (isIOSSafari() ? 'ios' : 'other');
45                trackPWAEvent('Open', 'standalone', {
46                    'pwa_platform': platform,
47                    'pwa_display_mode': 'standalone'
48                });
49            }
50        }
51
52        // Don't initialize PWA prompt in in-app browsers
53        if (isInAppBrowser()) {
54            // console.log('PWA: In-app browser detected, skipping PWA prompt initialization');
55            // Still register service worker but don't show prompts
56            initServiceWorker();
57            return;
58        }
59
60        initServiceWorker();
61        createInstallPrompt();
62        setupInstallPromptListeners();
63
64        // If beforeinstallprompt already fired before DOM loaded, update button
65        if (beforeInstallPromptFired && deferredPrompt) {
66            updateInstallButtonForNative();
67        }
68
69        // Add global function for testing
70        window.testPWAPrompt = function () {
71            // console.log('PWA: Manual test triggered');
72            showInstallPrompt();
73        };
74
75        // Also add debug functions
76        window.pwaDEBUG = {
77            showPrompt: showInstallPrompt,
78            hidePrompt: hideInstallPrompt,
79            showAndroidInstructions: showAndroidInstallInstructions,
80            showGuidedInstall: showAndroidGuidedInstall,
81            checkConditions: function () {
82                // console.log('PWA Conditions Check:');
83                // console.log('- Is Mobile:', isMobileDevice());
84                // console.log('- Is In-App Browser:', isInAppBrowser());
85                // console.log('- Is Android:', isAndroidDevice());
86                // console.log('- Is Android Chrome:', isAndroidChrome());
87                // console.log('- Is Android Edge:', isAndroidEdge());
88                // console.log('- Supports Native Install:', supportsNativeInstall());
89                // console.log('- Browser Name:', getBrowserName());
90                // console.log('- Is Standalone:', isStandalone());
91                // console.log('- Is Dismissed:', isDismissed());
92                // console.log('- Deferred Prompt:', deferredPrompt);
93            }
94        };
95
96
97        // console.log('PWA: You can test the prompt manually by running: testPWAPrompt()');
98
99        // Additional check for PWA installability criteria
100        setTimeout(function () {
101            checkPWAInstallCriteria();
102        }, 2000);
103    });
104
105    // Register service worker
106    function initServiceWorker() {
107        if ('serviceWorker' in navigator) {
108            // Register with explicit scope to ensure PWA installability
109            navigator.serviceWorker.register(simplePWA.serviceWorkerUrl, {
110                scope: '/'
111            })
112                .then(function (registration) {
113                    // console.log('Service Worker registered successfully:', registration.scope);
114
115                    // Check for updates periodically
116                    registration.update();
117
118                    // When service worker is active, Chrome may fire beforeinstallprompt
119                    if (registration.active) {
120                        // console.log('PWA: Service worker is active');
121                    }
122
123                    // Listen for service worker state changes
124                    registration.addEventListener('updatefound', function () {
125                        const newWorker = registration.installing;
126                        if (newWorker) {
127                            newWorker.addEventListener('statechange', function () {
128                                if (newWorker.state === 'activated') {
129                                    // console.log('PWA: New service worker activated');
130                                }
131                            });
132                        }
133                    });
134                })
135                .catch(function (error) {
136                    // console.log('Service Worker registration failed:', error);
137                });
138        }
139    }
140
141    // Create install prompt UI
142    function createInstallPrompt() {
143        // console.log('PWA: Creating install prompt UI');
144        const promptHTML = `
145            <div id="pwa-install-prompt" class="pwa-install-prompt hidden">
146                <div class="pwa-prompt-content">
147                    <div class="pwa-prompt-icon" id="pwa-prompt-icon"></div>
148                    <div class="pwa-prompt-text">
149                        <h3>${simplePWA.siteName || simplePWA.appName}</h3>
150                        <p>${simplePWA.promptDescription || 'Voeg toe aan je startscherm!'}</p>
151                    </div>
152                    <div class="pwa-prompt-buttons">
153                        <button id="pwa-install-btn" class="pwa-btn pwa-btn-primary">${simplePWA.installCta || 'Installeren'}</button>
154                        <button id="pwa-close-btn" class="pwa-close-btn">&times;</button>
155                    </div>
156                </div>
157            </div>
158        `;
159
160        document.body.insertAdjacentHTML('beforeend', promptHTML);
161        installButton = document.getElementById('pwa-install-btn');
162
163        // console.log('PWA: Prompt HTML inserted into DOM');
164        // console.log('PWA: Install button element:', installButton);
165
166        // Verify the prompt was added
167        const addedPrompt = document.getElementById('pwa-install-prompt');
168        if (addedPrompt) {
169            // console.log('PWA: Prompt element successfully added to DOM');
170        } else {
171            // console.log('PWA: ERROR - Failed to add prompt element to DOM');
172        }
173
174        // Load the appropriate icon
175        loadPromptIcon();
176    }
177
178    // Setup event listeners for install prompt
179    function setupInstallPromptListeners() {
180        const installPrompt = document.getElementById('pwa-install-prompt');
181        const closeBtn = document.getElementById('pwa-close-btn');
182
183        // Note: Main beforeinstallprompt listener is registered at the top level (before DOMContentLoaded)
184        // This secondary listener handles late-firing scenarios
185        window.addEventListener('beforeinstallprompt', function (e) {
186            // console.log('PWA: beforeinstallprompt fired (secondary listener) - native install available');
187            e.preventDefault();
188            deferredPrompt = e;
189            beforeInstallPromptFired = true;
190
191            // Update existing prompt if it's already showing
192            if (document.getElementById('pwa-install-prompt') && !document.getElementById('pwa-install-prompt').classList.contains('hidden')) {
193                // console.log('PWA: Updating existing prompt with native functionality');
194                updateInstallButtonForNative();
195            }
196            // Show install prompt if not dismissed and not already showing
197            else if (!isDismissed() && !isStandalone()) {
198                setTimeout(function () {
199                    // console.log('PWA: Showing prompt with native install capability');
200                    showInstallPrompt();
201                    updateInstallButtonForNative();
202                }, 2000);
203            }
204        });
205
206        // Install button click
207        if (installButton) {
208            installButton.addEventListener('click', async function () {
209                // console.log('PWA: Install button clicked, deferredPrompt available:', !!deferredPrompt);
210
211                if (isAndroidDevice()) {
212                    const platform = 'android';
213                    trackPWAEvent('Install Click', 'install_click', {
214                        'pwa_platform': platform,
215                        'pwa_browser': getBrowserName()
216                    });
217                }
218
219                if (deferredPrompt) {
220                    // console.log('PWA: Using native install prompt');
221                    try {
222                        deferredPrompt.prompt();
223                        const choiceResult = await deferredPrompt.userChoice;
224                        if (choiceResult.outcome === 'accepted') {
225                            // console.log('User accepted the install prompt');
226                            // Track successful install
227                            const platform = isAndroidDevice() ? 'android' : (isIOSSafari() ? 'ios' : 'other');
228                            trackPWAEvent('Install Success', 'install_success', {
229                                'pwa_platform': platform,
230                                'pwa_browser': getBrowserName()
231                            });
232                        } else {
233                            // console.log('User dismissed the install prompt');
234                        }
235                        deferredPrompt = null;
236                        hideInstallPrompt();
237                    } catch (err) {
238                        // console.log('PWA: Native prompt error:', err);
239                        // Prompt may have been used already, fall back to guided install
240                        deferredPrompt = null;
241                        showFallbackInstall();
242                    }
243                } else {
244                    // console.log('PWA: No native prompt available, checking if installed...');
245
246                    // Check if app is already installed
247                    if ('getInstalledRelatedApps' in navigator) {
248                        try {
249                            const relatedApps = await navigator.getInstalledRelatedApps();
250                            // If we find any related app (since we added ourselves to manifest), assume it's installed
251                            if (relatedApps.length > 0) {
252                                // App is installed - try to open it by navigating to start_url
253                                // console.log('PWA: App detected as installed, attempting to open...', relatedApps);
254                                const openUrl = simplePWA.startUrl || '/?utm_source=pwa&utm_medium=standalone';
255                                window.location.href = openUrl;
256                                return;
257                            }
258                        } catch (err) {
259                            // console.log('PWA: Error checking installed apps:', err);
260                        }
261                    }
262
263                    // console.log('PWA: No native prompt available, showing guided installation');
264                    showFallbackInstall();
265                }
266            });
267        }
268
269        // Helper function to show fallback installation
270        function showFallbackInstall() {
271            // For Android devices, show a guided approach
272            if (isAndroidDevice()) {
273                // console.log('PWA: Android detected, showing guided installation');
274                hideInstallPrompt();
275                showAndroidGuidedInstall();
276            }
277            // Handle different scenarios based on device type
278            else if (isIOSSafari()) {
279                // Fallback for iOS Safari
280                showIOSInstallInstructions();
281            } else {
282                // Generic fallback
283                showGenericInstallInstructions();
284            }
285        }
286
287        // Close button click
288        if (closeBtn) {
289            closeBtn.addEventListener('click', function () {
290                const platform = isAndroidDevice() ? 'android' : (isIOSSafari() ? 'ios' : 'other');
291                trackPWAEvent('Popup Closed', 'dismissed', {
292                    'pwa_platform': platform,
293                    'pwa_browser': getBrowserName()
294                });
295                hideInstallPrompt();
296                // Don't show again for 30 days
297                localStorage.setItem('pwa-dismissed', Date.now() + (30 * 24 * 60 * 60 * 1000));
298            });
299        }
300
301        // Listen for app installed event
302        window.addEventListener('appinstalled', function (e) {
303            // console.log('PWA was installed');
304            hideInstallPrompt();
305            localStorage.setItem('pwa-installed', '1');
306        });
307
308        // Debug information
309        // console.log('PWA Debug Info:');
310        // console.log('- Is Mobile:', isMobileDevice());
311        // console.log('- Is Standalone:', isStandalone());
312        // console.log('- Is Dismissed:', isDismissed());
313        // console.log('- Is iOS Safari:', isIOSSafari());
314        // console.log('- Is Android:', isAndroidDevice());
315        // console.log('- User Agent:', navigator.userAgent);
316
317        // Check if we should show prompt on mobile devices
318        if (isMobileDevice() && !isStandalone() && !isDismissed()) {
319            // console.log('PWA: Conditions met, will check if already installed');
320
321            // Check if app is already installed before showing prompt
322            isAppAlreadyInstalled().then(function (installed) {
323                if (installed) {
324                    // console.log('PWA: App already installed, not showing prompt');
325                    return;
326                }
327
328                // For Android devices - prioritize native install prompt
329                if (isAndroidDevice()) {
330                    // console.log('PWA: Android detected, setting up install functionality');
331
332                    // If we already have the native prompt, show immediately with a small delay
333                    if (deferredPrompt) {
334                        // console.log('PWA: Native prompt already available, showing promptly');
335                        setTimeout(function () {
336                            showInstallPrompt();
337                            updateInstallButtonForNative();
338                        }, 2000);
339                    } else {
340                        // Wait a bit longer for beforeinstallprompt to potentially fire
341                        // Chrome needs the service worker to be active first
342                        // console.log('PWA: Waiting for native prompt...');
343                        setTimeout(function () {
344                            if (deferredPrompt) {
345                                // console.log('PWA: Native prompt became available');
346                                showInstallPrompt();
347                                updateInstallButtonForNative();
348                            } else {
349                                // console.log('PWA: No native prompt after wait, showing anyway');
350                                // Show prompt anyway - button click will handle fallback
351                                showInstallPrompt();
352                            }
353                        }, 4000); // Wait 4 seconds to give Chrome time to fire beforeinstallprompt
354                    }
355                }
356                // Show prompt for iOS Safari users (no beforeinstallprompt event)
357                else if (isIOSSafari()) {
358                    // console.log('PWA: iOS Safari detected, showing prompt in 3 seconds');
359                    setTimeout(function () {
360                        showInstallPrompt();
361                    }, 3000);
362                }
363                // Also show for other mobile devices
364                else {
365                    // console.log('PWA: Other mobile device, showing prompt in 4 seconds');
366                    setTimeout(function () {
367                        showInstallPrompt();
368                    }, 4000);
369                }
370            });
371        } else {
372            // console.log('PWA: Conditions not met for showing prompt');
373            // if (!isMobileDevice()) console.log('- Not a mobile device');
374            // if (isStandalone()) console.log('- Already running in standalone mode');
375            // if (isDismissed()) console.log('- User previously dismissed the prompt');
376        }
377    }
378
379    // Show install prompt
380    function showInstallPrompt() {
381        // console.log('PWA: showInstallPrompt() called');
382
383        // Don't show prompt in in-app browsers
384        if (isInAppBrowser()) {
385            // console.log('PWA: In-app browser detected, not showing prompt');
386            return;
387        }
388
389        // Don't show prompt if already in standalone mode
390        if (isStandalone()) {
391            // console.log('PWA: Already in standalone mode, not showing prompt');
392            return;
393        }
394
395        // Don't show prompt on desktop
396        if (!isMobileDevice()) {
397            // console.log('PWA: Desktop detected, not showing prompt');
398            return;
399        }
400
401        if (!canShowPromptThisSession()) {
402            // console.log('PWA: Session limit reached, not showing prompt');
403            return;
404        }
405        const prompt = document.getElementById('pwa-install-prompt');
406        if (prompt) {
407            // console.log('PWA: Prompt element found, showing prompt');
408            prompt.classList.remove('hidden');
409            prompt.classList.add('show');
410            incrementSessionShowCounter();
411        } else {
412            // console.log('PWA: ERROR - Prompt element not found!');
413        }
414    }
415
416    // Hide install prompt
417    function hideInstallPrompt() {
418        const prompt = document.getElementById('pwa-install-prompt');
419        if (prompt) {
420            prompt.classList.remove('show');
421            prompt.classList.add('hidden');
422        }
423    }
424
425    // Session limit helpers
426    function getSessionCounterKey() {
427        return 'pwa-session-shows';
428    }
429    function getSessionExpiryKey() {
430        return 'pwa-session-expires';
431    }
432    function getNow() { return Date.now(); }
433    function getLimit() { return Number(simplePWA.maxShowsPerSession || 0); }
434    function getTtl() { return Number(simplePWA.sessionLimitLifetimeMs || 0); }
435
436    function ensureSessionWindow() {
437        const expiryKey = getSessionExpiryKey();
438        const expiresAt = Number(localStorage.getItem(expiryKey) || 0);
439        const now = getNow();
440        if (!expiresAt || now > expiresAt) {
441            // Reset window
442            localStorage.setItem(getSessionCounterKey(), '0');
443            const ttl = getTtl();
444            if (ttl > 0) {
445                localStorage.setItem(expiryKey, String(now + ttl));
446            } else {
447                // If no ttl provided, default to 1 hour
448                localStorage.setItem(expiryKey, String(now + 60 * 60 * 1000));
449            }
450        }
451    }
452
453    function canShowPromptThisSession() {
454        const limit = getLimit();
455        if (limit <= 0) return false; // never show
456        ensureSessionWindow();
457        const count = Number(localStorage.getItem(getSessionCounterKey()) || 0);
458        return count < limit;
459    }
460
461    function incrementSessionShowCounter() {
462        ensureSessionWindow();
463        const key = getSessionCounterKey();
464        const count = Number(localStorage.getItem(key) || 0) + 1;
465        localStorage.setItem(key, String(count));
466    }
467
468    // Load app icon for iOS modal
469    function loadIOSModalIcon() {
470        const iconElements = document.querySelectorAll('.ios-modal-app-icon');
471        if (!iconElements || iconElements.length === 0) return;
472
473        // Priority 1: admin-provided icon via localized settings
474        if (simplePWA && simplePWA.customIcon192) {
475            iconElements.forEach(el => {
476                el.src = simplePWA.customIcon192;
477            });
478            return;
479        }
480
481        // Priority 2: WP app manifest
482        fetch('/wp-json/app/manifest')
483            .then(response => {
484                if (!response.ok) throw new Error('Manifest not available');
485                return response.json();
486            })
487            .then(manifest => {
488                if (manifest.icons && manifest.icons.length > 0) {
489                    // Find the best icon (prefer 192x192 or closest)
490                    let bestIcon = manifest.icons[0];
491                    for (const icon of manifest.icons) {
492                        if (icon.sizes === '192x192' || icon.sizes === '192') {
493                            bestIcon = icon;
494                            break;
495                        }
496                    }
497
498                    iconElements.forEach(el => {
499                        el.src = bestIcon.src;
500                    });
501                } else {
502                    // Fallback to site manifest
503                    loadIOSModalIconFromSiteManifest(iconElements);
504                }
505            })
506            .catch(() => {
507                // Fallback to site manifest
508                loadIOSModalIconFromSiteManifest(iconElements);
509            });
510    }
511
512    // Load icon from site's manifest.json for iOS modal
513    function loadIOSModalIconFromSiteManifest(iconElements) {
514        fetch('/?simple_pwa_manifest=1')
515            .then(response => {
516                if (!response.ok) throw new Error('Site manifest not available');
517                return response.json();
518            })
519            .then(manifest => {
520                if (manifest.icons && manifest.icons.length > 0) {
521                    // Find the best icon (prefer 192x192 or closest)
522                    let bestIcon = manifest.icons[0];
523                    for (const icon of manifest.icons) {
524                        if (icon.sizes === '192x192') {
525                            bestIcon = icon;
526                            break;
527                        }
528                    }
529
530                    iconElements.forEach(el => {
531                        el.src = bestIcon.src;
532                    });
533                }
534                // If no icons found, use fallback (already set in HTML)
535            })
536            .catch(() => {
537                // Keep fallback icon if everything fails
538                // console.log('PWA: Using fallback icon for iOS modal');
539            });
540    }
541
542    // Show iOS install instructions
543    function showIOSInstallInstructions() {
544        const custom = (simplePWA.instructions && simplePWA.instructions.ios) ? simplePWA.instructions.ios : '';
545
546        // Get plugin URL for icon assets
547        const pluginUrl = simplePWA.pluginUrl || (simplePWA.homeUrl + '/wp-content/plugins/simple-pwa');
548
549        // Fallback app icon
550        const fallbackIconUrl = pluginUrl + '/assets/icon-192x192.svg';
551
552        // Get the actual site name (not the prompt title)
553        const appName = simplePWA.appName || simplePWA.siteName;
554
555        const instructions = (custom && custom.trim().length > 0) ? custom : `
556            <div id="ios-install-modal" class="pwa-modal no-blurring">
557                <div class="pwa-modal-content-ios">
558                    <div class="pwa-modal-header-ios">
559                        <div class="pwa-modal-title-ios">Toevoegen aan startscherm</div>
560                        <button class="pwa-modal-close-ios" id="ios-modal-close">
561                            <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
562                                <line x1="18" y1="6" x2="6" y2="18"></line>
563                                <line x1="6" y1="6" x2="18" y2="18"></line>
564                            </svg>
565                        </button>
566                    </div>
567                    
568                    <div class="pwa-modal-details-ios">
569                        <div class="pwa-modal-img-ios bordered">
570                            <img alt="app icon" class="ios-modal-app-icon" style="color:transparent !important;" src="${fallbackIconUrl}">
571                        </div>
572                        <div class="pwa-modal-text-ios">
573                            <div>${appName}</div>
574                            <div>${simplePWA.homeUrl.replace(/^https?:\/\//, '')}</div>
575                        </div>
576                    </div>
577                    
578                    <div class="pwa-modal-steps-ios">
579                        <div class="pwa-modal-step-ios" data-step="1.">
580                            <div>
581                                Klik op 
582                                <div class="apple-share-sheet-icon">
583                                    <img src="${pluginUrl}/assets/icons/ios-share.svg" alt="Share" width="24" height="24">
584                                </div>
585                                in de werkbalk van de browser
586                            </div>
587                        </div>
588                        
589                        <div class="pwa-modal-step-ios" data-step="2.">
590                            <div>
591                                Scroll en selecteer 
592                                <span class="add-to-homescreen-text">
593                                    <img src="${pluginUrl}/assets/icons/plus-square.svg" alt="Add" width="18" height="18">
594                                    Zet op beginscherm
595                                </span>
596                            </div>
597                        </div>
598                        
599                        <div class="pwa-modal-step-ios" data-step="3.">
600                            <div>
601                                Ga naar het 
602                                <img alt="app icon" class="pwa-inline-logo ios-modal-app-icon" src="${fallbackIconUrl}" style="color:transparent !important;"> 
603                                icoon op je startscherm
604                            </div>
605                        </div>
606                    </div>
607                </div>
608            </div>
609        `;
610
611        document.body.insertAdjacentHTML('beforeend', instructions);
612
613        // Load the proper app icon
614        loadIOSModalIcon();
615
616        const modal = document.getElementById('ios-install-modal');
617        const closeBtn = document.getElementById('ios-modal-close');
618
619        if (closeBtn) {
620            closeBtn.addEventListener('click', function () {
621                modal.remove();
622                hideInstallPrompt();
623            });
624        }
625
626        // Close modal when clicking outside
627        if (modal) {
628            modal.addEventListener('click', function (e) {
629                if (e.target === modal) {
630                    modal.remove();
631                    hideInstallPrompt();
632                }
633            });
634        }
635    }
636
637    // Show Android install instructions
638    function showAndroidInstallInstructions() {
639        const browserName = getBrowserName();
640        const custom = (simplePWA.instructions && simplePWA.instructions.android) ? simplePWA.instructions.android : '';
641        const appName = simplePWA.appName || simplePWA.siteName;
642        const instructions = (custom && custom.trim().length > 0) ? custom : `
643            <div id="android-install-modal" class="pwa-modal">
644                <div class="pwa-modal-content">
645                    <h3>${appName}</h3>
646                    <div class="android-instructions">
647                        <p>${simplePWA.promptDescription || 'Voeg toe aan je startscherm!'}</p>
648                        ${getAndroidInstructions(browserName)}
649                    </div>
650                    <button id="android-modal-close" class="pwa-btn pwa-btn-primary">Begrepen</button>
651                </div>
652            </div>
653        `;
654
655        document.body.insertAdjacentHTML('beforeend', instructions);
656
657        const modal = document.getElementById('android-install-modal');
658        const closeBtn = document.getElementById('android-modal-close');
659
660        if (closeBtn) {
661            closeBtn.addEventListener('click', function () {
662                modal.remove();
663                hideInstallPrompt();
664            });
665        }
666    }
667
668    // Show generic install instructions
669    function showGenericInstallInstructions() {
670        const custom = (simplePWA.instructions && simplePWA.instructions.generic) ? simplePWA.instructions.generic : '';
671        const appName = simplePWA.appName || simplePWA.siteName;
672        const instructions = (custom && custom.trim().length > 0) ? custom : `
673            <div id="generic-install-modal" class="pwa-modal">
674                <div class="pwa-modal-content">
675                    <h3>${appName}</h3>
676                    <div class="generic-instructions">
677                        <p>${simplePWA.promptDescription || 'Voeg toe aan je startscherm!'}</p>
678                        <ul>
679                            <li>Zoek naar een "Toevoegen aan startscherm" optie in je browser menu</li>
680                            <li>Of voeg deze pagina toe aan je bladwijzers voor snelle toegang</li>
681                        </ul>
682                    </div>
683                    <button id="generic-modal-close" class="pwa-btn pwa-btn-primary">Begrepen</button>
684                </div>
685            </div>
686        `;
687
688        document.body.insertAdjacentHTML('beforeend', instructions);
689
690        const modal = document.getElementById('generic-install-modal');
691        const closeBtn = document.getElementById('generic-modal-close');
692
693        if (closeBtn) {
694            closeBtn.addEventListener('click', function () {
695                modal.remove();
696                hideInstallPrompt();
697            });
698        }
699    }
700
701    // Show waiting message for delayed install
702    function showWaitingForInstall() {
703        const waitingHTML = `
704            <div id="pwa-waiting-modal" class="pwa-modal">
705                <div class="pwa-modal-content">
706                    <div class="pwa-waiting-content">
707                        <div class="pwa-spinner"></div>
708                        <h3>Even wachten...</h3>
709                        <p>We controleren of de app direct geïnstalleerd kan worden.</p>
710                    </div>
711                </div>
712            </div>
713        `;
714
715        document.body.insertAdjacentHTML('beforeend', waitingHTML);
716    }
717
718    // Hide waiting message
719    function hideWaitingMessage() {
720        const waitingModal = document.getElementById('pwa-waiting-modal');
721        if (waitingModal) {
722            waitingModal.remove();
723        }
724    }
725
726    // Show Android guided installation that actually works
727    function showAndroidGuidedInstall() {
728        // console.log('PWA: Showing Android guided installation');
729
730        const browserName = getBrowserName();
731        const isChrome = browserName.includes('Chrome');
732
733        // Get the actual site name from wp-json/app/manifest or fallback
734        const appName = simplePWA.appName || simplePWA.siteName;
735
736        // IMPORTANT: Guide users to "Install app" instead of "Add to Home screen" when possible
737        // "Install app" creates a full PWA, "Add to Home screen" may create just a shortcut
738        const guidedHTML = `
739            <div id="pwa-guided-modal" class="pwa-modal">
740                <div class="pwa-modal-content pwa-modal-content-android">
741                    <div class="pwa-modal-header-android">
742                        <div class="pwa-modal-title-android">Installeer ${appName}</div>
743                        <button class="pwa-modal-close-android" onclick="document.getElementById('pwa-guided-modal').remove()">
744                            <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
745                                <line x1="18" y1="6" x2="6" y2="18"></line>
746                                <line x1="6" y1="6" x2="18" y2="18"></line>
747                            </svg>
748                        </button>
749                    </div>
750                    
751                    <div class="pwa-guided-content-android">
752                        <div class="pwa-guided-steps-android">
753                            ${isChrome ? `
754                                <div class="pwa-step-android">
755                                    <div class="pwa-step-number-android">1</div>
756                                    <div class="pwa-step-content-android">
757                                        <div class="pwa-step-title-android">Tik op het menu rechtsboven</div>
758                                        <div class="pwa-step-visual-android">
759                                            <div class="pwa-browser-bar-android">
760                                                <div class="pwa-address-bar-android">${window.location.hostname}</div>
761                                                <div class="pwa-menu-dots-android">
762                                                    <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="12" cy="19" r="2"/></svg>
763                                                </div>
764                                            </div>
765                                        </div>
766                                    </div>
767                                </div>
768                                
769                                <div class="pwa-step-android">
770                                    <div class="pwa-step-number-android">2</div>
771                                    <div class="pwa-step-content-android">
772                                        <div class="pwa-step-title-android">Kies <strong>"App installeren"</strong></div>
773                                        <div class="pwa-step-description-android">Als je "App installeren" niet ziet, kies dan "Toevoegen aan startscherm"</div>
774                                        <div class="pwa-step-visual-android">
775                                            <div class="pwa-menu-item-android pwa-menu-item-preferred">
776                                                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
777                                                <span>App installeren</span>
778                                            </div>
779                                        </div>
780                                    </div>
781                                </div>
782                                
783                                <div class="pwa-step-android">
784                                    <div class="pwa-step-number-android">3</div>
785                                    <div class="pwa-step-content-android">
786                                        <div class="pwa-step-title-android">Bevestig met "Installeren"</div>
787                                    </div>
788                                </div>
789                            ` : `
790                                <div class="pwa-step-android">
791                                    <div class="pwa-step-number-android">1</div>
792                                    <div class="pwa-step-content-android">
793                                        <div class="pwa-step-title-android">Tik op het menu van je browser</div>
794                                        <div class="pwa-step-visual-android">
795                                            <div class="pwa-browser-bar-android">
796                                                <div class="pwa-address-bar-android">${window.location.hostname}</div>
797                                                <div class="pwa-menu-icon-android">
798                                                    <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><rect x="3" y="5" width="18" height="2"/><rect x="3" y="11" width="18" height="2"/><rect x="3" y="17" width="18" height="2"/></svg>
799                                                </div>
800                                            </div>
801                                        </div>
802                                    </div>
803                                </div>
804                                
805                                <div class="pwa-step-android">
806                                    <div class="pwa-step-number-android">2</div>
807                                    <div class="pwa-step-content-android">
808                                        <div class="pwa-step-title-android">Kies <strong>"App installeren"</strong> of "Toevoegen aan startscherm"</div>
809                                        <div class="pwa-step-visual-android">
810                                            <div class="pwa-menu-item-android pwa-menu-item-preferred">
811                                                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
812                                                <span>App installeren</span>
813                                            </div>
814                                        </div>
815                                    </div>
816                                </div>
817                            `}
818                        </div>
819                    </div>
820                </div>
821            </div>
822        `;
823
824        document.body.insertAdjacentHTML('beforeend', guidedHTML);
825
826        // Close on backdrop click
827        const modal = document.getElementById('pwa-guided-modal');
828        if (modal) {
829            modal.addEventListener('click', function (e) {
830                if (e.target === modal) {
831                    modal.remove();
832                }
833            });
834        }
835    }
836
837
838    // Create direct homescreen shortcut for Android
839    function createHomescreenShortcut() {
840        // console.log('PWA: Attempting to create direct homescreen shortcut');
841
842        // Show success message
843        showShortcutSuccess();
844
845        // Try to trigger browser's "Add to Home Screen" functionality
846        // This uses a combination of techniques
847
848        // Method 1: Try to programmatically trigger the menu
849        if (navigator.userAgent.includes('Chrome')) {
850            // For Chrome, we can try to guide the user more directly
851            setTimeout(function () {
852                // Create a more direct approach
853                triggerChromeAddToHomeScreen();
854            }, 1000);
855        } else {
856            // For other browsers, show a simple success message
857            setTimeout(function () {
858                showGenericShortcutMessage();
859            }, 1000);
860        }
861    }
862
863    // Trigger Chrome's Add to Home Screen
864    function triggerChromeAddToHomeScreen() {
865        // console.log('PWA: Triggering Chrome Add to Home Screen');
866
867        try {
868            // Method 1: Try to create a virtual link click to trigger browser menu
869            const link = document.createElement('a');
870            link.href = window.location.href;
871            link.setAttribute('data-turbo', 'false');
872            link.click();
873
874            // Method 2: Try to trigger browser's context menu programmatically
875            const contextMenuEvent = new MouseEvent('contextmenu', {
876                bubbles: true,
877                cancelable: true,
878                view: window,
879                clientX: window.innerWidth / 2,
880                clientY: window.innerHeight / 2
881            });
882            document.body.dispatchEvent(contextMenuEvent);
883
884            // Method 3: Try to simulate key combinations that might trigger browser menu
885            const keyEvent = new KeyboardEvent('keydown', {
886                key: 'F10',
887                shiftKey: true,
888                bubbles: true
889            });
890            document.dispatchEvent(keyEvent);
891
892            // Method 4: Force refresh manifest link
893            const existingManifest = document.querySelector('link[rel="manifest"]');
894            if (existingManifest) {
895                existingManifest.remove();
896            }
897            const newManifest = document.createElement('link');
898            newManifest.rel = 'manifest';
899            newManifest.href = simplePWA.manifestUrl + '?v=' + Date.now();
900            document.head.appendChild(newManifest);
901
902            // Method 5: Try to trigger through window.open with special parameters
903            setTimeout(function () {
904                try {
905                    const popup = window.open('', '_self');
906                    if (popup) {
907                        popup.close();
908                    }
909                } catch (e) {
910                    // console.log('PWA: Popup method failed:', e);
911                }
912            }, 100);
913
914            // Show success message after attempts
915            setTimeout(function () {
916                showShortcutCreatedMessage();
917            }, 2000);
918
919        } catch (error) {
920            // console.log('PWA: Direct shortcut creation failed:', error);
921            showShortcutCreatedMessage();
922        }
923    }
924
925    // Show shortcut success message
926    function showShortcutSuccess() {
927        const successHTML = `
928            <div id="pwa-shortcut-modal" class="pwa-modal">
929                <div class="pwa-modal-content">
930                    <div class="pwa-shortcut-content">
931                        <div class="pwa-success-icon">
932                            <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#34A853" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
933                        </div>
934                        <h3>Shortcut wordt aangemaakt...</h3>
935                        <p>We maken een snelkoppeling naar je startscherm.</p>
936                        <div class="pwa-spinner"></div>
937                    </div>
938                </div>
939            </div>
940        `;
941
942        document.body.insertAdjacentHTML('beforeend', successHTML);
943    }
944
945    // Show final shortcut created message
946    function showShortcutCreatedMessage() {
947        const modal = document.getElementById('pwa-shortcut-modal');
948        const appName = simplePWA.appName || simplePWA.siteName;
949        if (modal) {
950            const content = modal.querySelector('.pwa-shortcut-content');
951            content.innerHTML = `
952                <div class="pwa-success-icon">
953                    <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#34A853" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
954                </div>
955                <h3>Klaar</h3>
956                <p>De app is toegevoegd aan je startscherm.<br>Kijk naar het icoon van <strong>${appName}</strong>.</p>
957                <button class="pwa-btn pwa-btn-primary" onclick="this.closest('.pwa-modal').remove()">Sluiten</button>
958            `;
959        }
960
961        // Auto-close after 4 seconds
962        setTimeout(function () {
963            const modal = document.getElementById('pwa-shortcut-modal');
964            if (modal) {
965                modal.remove();
966            }
967        }, 4000);
968    }
969
970    // Show generic shortcut message for non-Chrome browsers
971    function showGenericShortcutMessage() {
972        const modal = document.getElementById('pwa-shortcut-modal');
973        const appName = simplePWA.appName || simplePWA.siteName;
974        if (modal) {
975            const content = modal.querySelector('.pwa-shortcut-content');
976            content.innerHTML = `
977                <div class="pwa-success-icon">
978                    <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#34A853" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
979                </div>
980                <h3>Shortcut aangemaakt</h3>
981                <p>De website is toegevoegd aan je startscherm als <strong>${appName}</strong>.</p>
982                <button class="pwa-btn pwa-btn-primary" onclick="this.closest('.pwa-modal').remove()">Sluiten</button>
983            `;
984        }
985
986        // Auto-close after 3 seconds
987        setTimeout(function () {
988            const modal = document.getElementById('pwa-shortcut-modal');
989            if (modal) {
990                modal.remove();
991            }
992        }, 3000);
993    }
994
995    // Update install button when native prompt becomes available
996    function updateInstallButtonForNative() {
997        const installBtn = document.getElementById('pwa-install-btn');
998        if (installBtn && deferredPrompt) {
999            // console.log('PWA: Updating install button for native Android prompt');
1000            installBtn.textContent = (simplePWA.installCta || 'Installeren');
1001            installBtn.style.background = '#34A853'; // Google green
1002
1003            // Keep plain text label (no emoji/icon)
1004            installBtn.innerHTML = (simplePWA.installCta || 'Installeren');
1005        }
1006    }
1007
1008    // Enhanced Android device detection
1009    function isAndroidChrome() {
1010        const ua = navigator.userAgent;
1011        return /Android/i.test(ua) && /Chrome/i.test(ua) && !/Edge/i.test(ua);
1012    }
1013
1014    function isAndroidEdge() {
1015        const ua = navigator.userAgent;
1016        return /Android/i.test(ua) && /Edge/i.test(ua);
1017    }
1018
1019    function supportsNativeInstall() {
1020        return isAndroidChrome() || isAndroidEdge();
1021    }
1022
1023    // Get browser name for Android instructions
1024    function getBrowserName() {
1025        const ua = navigator.userAgent;
1026        if (/Chrome/i.test(ua) && !/Edge/i.test(ua)) return 'Chrome';
1027        if (/Edge/i.test(ua)) return 'Edge';
1028        if (/Firefox/i.test(ua)) return 'Firefox';
1029        if (/Samsung/i.test(ua)) return 'Samsung Internet';
1030        return 'Browser';
1031    }
1032
1033    // Get specific Android installation instructions based on browser
1034    function getAndroidInstructions(browserName) {
1035        switch (browserName) {
1036            case 'Chrome':
1037                return `
1038                    <ol>
1039                        <li>Tik op het menu rechtsboven</li>
1040                        <li>Selecteer "Toevoegen aan startscherm"</li>
1041                        <li>Tik op "Toevoegen" om te bevestigen</li>
1042                    </ol>
1043                `;
1044            case 'Edge':
1045                return `
1046                    <ol>
1047                        <li>Tik op het menu onderaan</li>
1048                        <li>Selecteer "Toevoegen aan startscherm"</li>
1049                        <li>Tik op "Toevoegen" om te bevestigen</li>
1050                    </ol>
1051                `;
1052            case 'Firefox':
1053                return `
1054                    <ol>
1055                        <li>Tik op het menu rechtsboven</li>
1056                        <li>Selecteer "Toevoegen aan startscherm"</li>
1057                        <li>Tik op "Toevoegen"</li>
1058                    </ol>
1059                `;
1060            case 'Samsung Internet':
1061                return `
1062                    <ol>
1063                        <li>Tik op het menu onderaan</li>
1064                        <li>Selecteer "Toevoegen aan startscherm"</li>
1065                        <li>Bevestig de installatie</li>
1066                    </ol>
1067                `;
1068            default:
1069                return `
1070                    <ol>
1071                        <li>Zoek naar het menu van je browser</li>
1072                        <li>Zoek naar "Toevoegen aan startscherm"</li>
1073                        <li>Volg de instructies om de app te installeren</li>
1074                    </ol>
1075                `;
1076        }
1077    }
1078
1079    // Check PWA installation criteria
1080    function checkPWAInstallCriteria() {
1081        // console.log('PWA: Checking installation criteria...');
1082
1083        // Check service worker
1084        if ('serviceWorker' in navigator) {
1085            navigator.serviceWorker.getRegistration().then(function (registration) {
1086                // console.log('PWA Criteria - Service Worker:', registration ? '✅ Registered' : '❌ Not registered');
1087            });
1088        } else {
1089            // console.log('PWA Criteria - Service Worker: ❌ Not supported');
1090        }
1091
1092        // Check manifest
1093        const manifestLink = document.querySelector('link[rel="manifest"]');
1094        // console.log('PWA Criteria - Manifest:', manifestLink ? '✅ Present' : '❌ Missing');
1095
1096        // Check HTTPS
1097        // console.log('PWA Criteria - HTTPS:', location.protocol === 'https:' ? '✅ Secure' : '❌ Not secure');
1098
1099        // Check if already installed
1100        // console.log('PWA Criteria - Not installed:', !isStandalone() ? '✅ Not installed' : '❌ Already installed');
1101
1102        // Check beforeinstallprompt availability
1103        setTimeout(function () {
1104            // console.log('PWA Criteria - beforeinstallprompt:', deferredPrompt ? '✅ Available' : '❌ Not available');
1105
1106            if (!deferredPrompt && isAndroidChrome()) {
1107                // console.log('PWA: Chrome detected but no beforeinstallprompt. Possible reasons:');
1108                // console.log('- User previously dismissed the prompt');
1109                // console.log('- App is already installed');
1110                // console.log('- PWA criteria not fully met');
1111                // console.log('- User engagement score too low');
1112            }
1113        }, 5000);
1114    }
1115
1116    // Check if device is mobile
1117    function isMobileDevice() {
1118        return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
1119    }
1120
1121    // Check if PWA is already installed on the device
1122    async function isAppAlreadyInstalled() {
1123        // Check localStorage flag (set when appinstalled event fires)
1124        if (localStorage.getItem('pwa-installed') === '1') {
1125            return true;
1126        }
1127
1128        // Check via getInstalledRelatedApps API (Android Chrome)
1129        if ('getInstalledRelatedApps' in navigator) {
1130            try {
1131                const relatedApps = await navigator.getInstalledRelatedApps();
1132                if (relatedApps.length > 0) {
1133                    localStorage.setItem('pwa-installed', '1');
1134                    return true;
1135                }
1136            } catch (e) {
1137                // API not supported or failed, continue with normal flow
1138            }
1139        }
1140
1141        return false;
1142    }
1143
1144    // Check if app is running in standalone mode
1145    function isStandalone() {
1146        return window.matchMedia('(display-mode: standalone)').matches ||
1147            window.navigator.standalone === true;
1148    }
1149
1150    // Check if prompt was recently dismissed
1151    function isDismissed() {
1152        const dismissed = localStorage.getItem('pwa-dismissed');
1153        return dismissed && parseInt(dismissed) > Date.now();
1154    }
1155
1156    // Check if browser is iOS Safari
1157    function isIOSSafari() {
1158        const ua = window.navigator.userAgent;
1159        const iOS = !!ua.match(/iPad|iPhone|iPod/);
1160        const webkit = !!ua.match(/WebKit/);
1161        const chrome = !!ua.match(/CriOS|Chrome/);
1162        return iOS && webkit && !chrome;
1163    }
1164
1165    // Check if device is Android
1166    function isAndroidDevice() {
1167        const ua = window.navigator.userAgent;
1168        return /Android/i.test(ua);
1169    }
1170
1171    // Detect if user is in an in-app browser
1172    function isInAppBrowser() {
1173        const ua = window.navigator.userAgent || window.navigator.vendor || window.opera;
1174
1175        // Check for common in-app browsers
1176        const inAppBrowsers = [
1177            'FBAN',        // Facebook App
1178            'FBAV',        // Facebook App
1179            'Instagram',   // Instagram
1180            'LinkedInApp', // LinkedIn
1181            'Twitter',     // Twitter / X
1182            'TwitterAndroid', // Twitter Android App
1183            'Line',        // Line
1184            'Snapchat',    // Snapchat
1185            'Pinterest',   // Pinterest
1186            'WhatsApp',    // WhatsApp
1187            'Telegram',    // Telegram
1188            'messenger',   // Facebook Messenger
1189            'MicroMessenger', // WeChat
1190            'GSA',         // Google App (iOS)
1191        ];
1192
1193        // Check if any in-app browser identifier is in user agent
1194        for (let i = 0; i < inAppBrowsers.length; i++) {
1195            if (ua.indexOf(inAppBrowsers[i]) > -1) {
1196                // console.log('PWA: In-app browser detected: ' + inAppBrowsers[i]);
1197                return true;
1198            }
1199        }
1200
1201        // Additional check for iOS WebView
1202        if ((/iPad|iPhone|iPod/.test(ua)) && !window.MSStream) {
1203            // Check if it's not Safari
1204            const isSafari = /Safari/i.test(ua) && !/CriOS|FxiOS|OPiOS|mercury/i.test(ua);
1205            const isWebView = !isSafari && /AppleWebKit/i.test(ua);
1206
1207            if (isWebView) {
1208                // console.log('PWA: iOS WebView/In-app browser detected');
1209                return true;
1210            }
1211        }
1212
1213        return false;
1214    }
1215
1216    // Generic PWA event tracking function
1217    function trackPWAEvent(action, label, extras) {
1218        if (typeof window.dataLayer !== 'undefined') {
1219            window.dataLayer.push({
1220                'event': 'FireEvent',
1221                'eventCategory': 'PWA',
1222                'eventAction': action,
1223                'eventLabel': label || '',
1224                'eventValue': 1,
1225                ...(extras || {})
1226            });
1227        }
1228    }
1229
1230    // Load appropriate icon for the install prompt
1231    function loadPromptIcon() {
1232        const iconElement = document.getElementById('pwa-prompt-icon');
1233        if (!iconElement) return;
1234
1235        // Set a default SVG icon first
1236        iconElement.innerHTML = '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>';
1237
1238        // Priority 1: admin-provided icon via localized settings
1239        if (simplePWA && simplePWA.customIcon192) {
1240            const img = document.createElement('img');
1241            img.src = simplePWA.customIcon192;
1242            img.alt = 'App Icon';
1243            img.style.width = '24px';
1244            img.style.height = '24px';
1245            img.style.borderRadius = '4px';
1246            img.style.objectFit = 'cover';
1247            iconElement.innerHTML = '';
1248            iconElement.appendChild(img);
1249            return;
1250        }
1251
1252        // Priority 2: WP app manifest
1253        fetch('/wp-json/app/manifest')
1254            .then(response => {
1255                if (!response.ok) throw new Error('Manifest not available');
1256                return response.json();
1257            })
1258            .then(manifest => {
1259                if (manifest.icons && manifest.icons.length > 0) {
1260                    // Find the best icon (prefer 192x192 or closest)
1261                    let bestIcon = manifest.icons[0];
1262                    for (const icon of manifest.icons) {
1263                        if (icon.sizes === '192x192' || icon.sizes === '192') {
1264                            bestIcon = icon;
1265                            break;
1266                        }
1267                    }
1268
1269                    // Create img element to replace SVG
1270                    const img = document.createElement('img');
1271                    img.src = bestIcon.src;
1272                    img.alt = 'App Icon';
1273                    img.style.width = '24px';
1274                    img.style.height = '24px';
1275                    img.style.borderRadius = '4px';
1276                    img.style.objectFit = 'cover';
1277
1278                    // Replace the SVG with the image
1279                    iconElement.innerHTML = '';
1280                    iconElement.appendChild(img);
1281                } else {
1282                    // Fallback to trying site manifest
1283                    loadManifestIcon();
1284                }
1285            })
1286            .catch(() => {
1287                // Fallback to trying site manifest
1288                loadManifestIcon();
1289            });
1290    }
1291
1292    // Load icon from site's manifest.json
1293    function loadManifestIcon() {
1294        const iconElement = document.getElementById('pwa-prompt-icon');
1295        if (!iconElement) return;
1296
1297        fetch('/?simple_pwa_manifest=1')
1298            .then(response => {
1299                if (!response.ok) throw new Error('Site manifest not available');
1300                return response.json();
1301            })
1302            .then(manifest => {
1303                if (manifest.icons && manifest.icons.length > 0) {
1304                    // Find the best icon (prefer 192x192 or closest)
1305                    let bestIcon = manifest.icons[0];
1306                    for (const icon of manifest.icons) {
1307                        if (icon.sizes === '192x192') {
1308                            bestIcon = icon;
1309                            break;
1310                        }
1311                    }
1312
1313                    // Create img element to replace SVG
1314                    const img = document.createElement('img');
1315                    img.src = bestIcon.src;
1316                    img.alt = 'App Icon';
1317                    img.style.width = '24px';
1318                    img.style.height = '24px';
1319                    img.style.borderRadius = '4px';
1320                    img.style.objectFit = 'cover';
1321
1322                    // Replace the SVG with the image
1323                    iconElement.innerHTML = '';
1324                    iconElement.appendChild(img);
1325                }
1326                // If no icons found, keep the SVG fallback
1327            })
1328            .catch(() => {
1329                // Keep SVG fallback if everything fails
1330                // console.log('PWA: Using SVG fallback for install prompt icon');
1331            });
1332    }
1333
1334})();

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.