PageSourceSearch

https://cinnamon-brockley.co.uk/assets/js/menu-popup.js?v=1.2.4

js cinnamon-brockley.co.uk collected 2026-10-08 20:40:38 UTC 5,723 bytes, 168 lines download raw bytes

1/**
2 * Menu Popup
3 * Shows a modal carousel of promotional slides on menu page load,
4 * with dismissal memory (localStorage/sessionStorage).
5 *
6 * Expects:
7 *   window.MENU_POPUP - config from menu_popup.json (or null)
8 *   window.STORE_SLUG - store slug in multi-store, else null
9 *   #menuPopupModal   - DOM element rendered by server if slides exist
10 */
11(function () {
12    'use strict';
13
14    const MenuPopup = {
15        config: null,
16        modalEl: null,
17        modal: null,
18        carousel: null,
19        autoplayTimer: null,
20        isPaused: false,
21
22        init() {
23            this.config = window.MENU_POPUP || null;
24            this.modalEl = document.getElementById('menuPopupModal');
25
26            // Server decides whether to render the modal. If absent, nothing to do.
27            if (!this.modalEl) return;
28
29            // Config gate (redundant safety — if modal rendered, config was valid server-side)
30            if (!this.config || this.config.enabled === false) return;
31
32            // Dismissal gate
33            if (this.isDismissed()) return;
34
35            // Bootstrap instances
36            if (typeof bootstrap === 'undefined') {
37                console.warn('[MenuPopup] Bootstrap not loaded');
38                return;
39            }
40
41            this.modal = new bootstrap.Modal(this.modalEl, {
42                backdrop: true,
43                keyboard: true,
44                focus: true
45            });
46
47            const carouselEl = this.modalEl.querySelector('#menuPopupCarousel');
48            if (carouselEl) {
49                this.carousel = new bootstrap.Carousel(carouselEl, {
50                    interval: false,   // we control autoplay manually
51                    ride: false,
52                    touch: true,
53                    wrap: true
54                });
55            }
56
57            this.bindEvents();
58            this.show();
59        },
60
61        bindEvents() {
62            // Persist dismissal when modal closes (any trigger: X, backdrop, ESC)
63            this.modalEl.addEventListener('hidden.bs.modal', () => {
64                this.markDismissed();
65                this.stopAutoplay();
66            });
67
68            const carouselEl = this.modalEl.querySelector('#menuPopupCarousel');
69            if (!carouselEl) return;
70
71            // Pause autoplay on hover, resume on leave
72            carouselEl.addEventListener('mouseenter', () => { this.isPaused = true; });
73            carouselEl.addEventListener('mouseleave', () => { this.isPaused = false; });
74
75            // Reset autoplay timer on any manual navigation (clicks on controls or indicators)
76            const resetTimer = () => {
77                if (this.autoplayTimer) this.startAutoplay();
78            };
79            carouselEl.querySelectorAll('.carousel-control-prev, .carousel-control-next, .carousel-indicators button').forEach(el => {
80                el.addEventListener('click', resetTimer);
81            });
82        },
83
84        show() {
85            this.modal.show();
86
87            // Start autoplay only if carousel present (i.e. multiple slides)
88            const isMulti = this.modalEl.querySelectorAll('.carousel-item').length > 1;
89            if (isMulti && this.config.autoplay !== false) {
90                this.startAutoplay();
91            }
92        },
93
94        startAutoplay() {
95            this.stopAutoplay();
96            const interval = Math.max(2000, parseInt(this.config.autoplay_interval, 10) || 5000);
97            this.autoplayTimer = setInterval(() => {
98                if (this.isPaused || !this.carousel) return;
99                this.carousel.next();
100            }, interval);
101        },
102
103        stopAutoplay() {
104            if (this.autoplayTimer) {
105                clearInterval(this.autoplayTimer);
106                this.autoplayTimer = null;
107            }
108        },
109
110        /* -------- Dismissal memory -------- */
111
112        storageKey() {
113            const slug = window.STORE_SLUG || '_default';
114            return `menu_popup_dismissed_${slug}`;
115        },
116
117        isDismissed() {
118            // Session-only mode
119            if (this.config.show_once_per_session) {
120                try {
121                    return sessionStorage.getItem(this.storageKey()) === '1';
122                } catch (e) {
123                    return false;
124                }
125            }
126
127            // remember_days mode (default)
128            const days = parseInt(this.config.remember_days, 10);
129            if (!days || days <= 0) return false; // 0 = always show
130
131            try {
132                const stored = localStorage.getItem(this.storageKey());
133                if (!stored) return false;
134
135                const dismissedAt = parseInt(stored, 10);
136                if (!dismissedAt) return false;
137
138                const msSinceDismiss = Date.now() - dismissedAt;
139                const msInDays = days * 24 * 60 * 60 * 1000;
140                return msSinceDismiss < msInDays;
141            } catch (e) {
142                return false;
143            }
144        },
145
146        markDismissed() {
147            try {
148                if (this.config.show_once_per_session) {
149                    sessionStorage.setItem(this.storageKey(), '1');
150                } else {
151                    localStorage.setItem(this.storageKey(), Date.now().toString());
152                }
153            } catch (e) {
154                // localStorage/sessionStorage may be unavailable in private mode — fail silently
155            }
156        }
157    };
158
159    // Auto-init on DOM ready
160    if (document.readyState === 'loading') {
161        document.addEventListener('DOMContentLoaded', () => MenuPopup.init());
162    } else {
163        MenuPopup.init();
164    }
165
166    // Expose for debugging / manual reset
167    window.MenuPopup = MenuPopup;
168})();

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.