PageSourceSearch

https://cinnamon-brockley.co.uk/assets/js/a2hs.js

js cinnamon-brockley.co.uk collected 2026-10-08 20:40:29 UTC 5,664 bytes, 155 lines download raw bytes

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.