PageSourceSearch

https://peteseeger.org/wp-content/plugins/read-more-without-refresh/js/frontend.js?ver=4.2.1

js peteseeger.org collected 2026-09-25 00:25:42 UTC 36,565 bytes, 946 lines download raw bytes

1/**
2 * Read More Without Refresh Pro - Frontend (v5.0)
3 *
4 * Modules: toggle + animations, accordion groups, truncate ellipsis, lazy
5 * <template> rendering, conditional display, deep linking, remember state,
6 * expand/collapse all, A/B variants, content locker (email/share), engaged
7 * CTA injection, sections (Wikipedia mode) and REST analytics.
8 *
9 * No console output in production: debug logs only when WP_DEBUG is on.
10 *
11 * @package ReadMoreWithoutRefreshPro
12 * @version 5.0.0
13 */
14
15(function () {
16    'use strict';
17
18    var settings = window.rmwrSettings || {};
19
20    function debug() {
21        if (settings.debug && window.console) {
22            console.log.apply(console, ['RMWR:'].concat(Array.prototype.slice.call(arguments)));
23        }
24    }
25
26    /* ---------------------------------------------------------------------
27     * Storage helpers (fail silently in private mode)
28     * ------------------------------------------------------------------ */
29
30    function storeGet(key) {
31        try { return window.localStorage.getItem(key); } catch (e) { return null; }
32    }
33
34    function storeSet(key, value) {
35        try { window.localStorage.setItem(key, value); } catch (e) { /* noop */ }
36    }
37
38    /* ---------------------------------------------------------------------
39     * Analytics
40     * ------------------------------------------------------------------ */
41
42    function track(key, postId, event, variant) {
43        if (!settings.analytics || !settings.restUrl || !key) {
44            return;
45        }
46
47        try {
48            window.fetch(settings.restUrl.replace(/\/$/, '') + '/track', {
49                method: 'POST',
50                keepalive: true,
51                headers: { 'Content-Type': 'application/json' },
52                body: JSON.stringify({ k: key, p: parseInt(postId, 10) || 0, e: event, v: variant || '' })
53            }).catch(function () { /* never disturb the visitor */ });
54        } catch (e) { /* noop */ }
55
56        // Ad / analytics conversion events (GA4, Meta Pixel, Google Ads).
57        fireAdEvents(key, postId, event);
58
59        // Public hook for custom integrations.
60        try {
61            window.dispatchEvent(new CustomEvent('rmwr:' + event, {
62                detail: { instanceKey: key, postId: postId, variant: variant || '', timestamp: new Date().toISOString() }
63            }));
64        } catch (e) { /* noop */ }
65    }
66
67    /**
68     * Send a high-intent interaction to the site's ad / analytics platforms so
69     * engaged readers can be measured and retargeted. Fires only for the
70     * meaningful signals (expand, unlock, cta, share) and only for the
71     * platforms the admin enabled. All calls are wrapped so a missing or
72     * broken third-party tag can never disturb the visitor.
73     *
74     * @param {string} key    Instance key.
75     * @param {number} postId Post ID.
76     * @param {string} event  Interaction name.
77     */
78    function fireAdEvents(key, postId, event) {
79        var cfg = settings.adEvents;
80        if (!cfg || !cfg.enabled) {
81            return;
82        }
83        if (['expand', 'unlock', 'cta', 'share'].indexOf(event) === -1) {
84            return;
85        }
86
87        var payload = {
88            rmwr_event: event,
89            rmwr_key: key,
90            rmwr_post_id: parseInt(postId, 10) || 0
91        };
92
93        // Google Analytics 4 (gtag).
94        if (cfg.ga4 && typeof window.gtag === 'function') {
95            try { window.gtag('event', cfg.gaEventName || ('rmwr_' + event), payload); } catch (e) { /* noop */ }
96        }
97
98        // Meta / Facebook Pixel (custom event).
99        if (cfg.metaPixel && typeof window.fbq === 'function') {
100            try { window.fbq('trackCustom', cfg.metaEventName || 'RMWR_Engaged', payload); } catch (e) { /* noop */ }
101        }
102
103        // Google Ads conversion (needs a send_to id like 'AW-123456789/AbC-D_efG').
104        if (cfg.googleAds && cfg.googleAdsSendTo && typeof window.gtag === 'function') {
105            try { window.gtag('event', 'conversion', { send_to: cfg.googleAdsSendTo }); } catch (e) { /* noop */ }
106        }
107
108        // Generic dataLayer push for GTM users.
109        if (cfg.dataLayer && window.dataLayer && typeof window.dataLayer.push === 'function') {
110            try { window.dataLayer.push({ event: 'rmwr_' + event, rmwrKey: key, rmwrPostId: payload.rmwr_post_id }); } catch (e) { /* noop */ }
111        }
112    }
113
114    /* ---------------------------------------------------------------------
115     * A/B variants
116     * ------------------------------------------------------------------ */
117
118    var abVariant = '';
119
120    function initAB() {
121        var variants = settings.abVariants || [];
122        if (!variants.length || variants.length < 2) {
123            return;
124        }
125
126        var idx = parseInt(storeGet('rmwr_ab_idx'), 10);
127        if (isNaN(idx) || idx < 0 || idx >= variants.length) {
128            idx = Math.floor(Math.random() * variants.length);
129            storeSet('rmwr_ab_idx', String(idx));
130        }
131
132        abVariant = variants[idx];
133
134        document.querySelectorAll('.read-link[data-ab="1"]').forEach(function (link) {
135            var textEl = link.querySelector('.rmwr-text');
136            if (textEl && link.getAttribute('aria-expanded') !== 'true') {
137                textEl.textContent = abVariant;
138            }
139            link.dataset.openText = abVariant;
140        });
141
142        debug('A/B variant assigned:', abVariant);
143    }
144
145    /* ---------------------------------------------------------------------
146     * Animations (expand/collapse)
147     * ------------------------------------------------------------------ */
148
149    function clearAnimStyles(el) {
150        el.style.maxHeight = '';
151        el.style.overflow = '';
152        el.style.transform = '';
153        el.style.perspective = '';
154        el.style.transformOrigin = '';
155        el.style.transition = '';
156        el.style.opacity = '';
157    }
158
159    function expandEl(element, wrapper, instant) {
160        element.style.display = 'block';
161
162        var animation = instant ? 'none' : ((wrapper && wrapper.dataset.animation) || 'none');
163        var duration = parseInt(wrapper ? wrapper.dataset.duration : 0, 10) || 300;
164
165        if (animation === 'none') {
166            return;
167        }
168
169        element.style.overflow = 'hidden';
170        element.style.transition = 'all ' + duration + 'ms ease-in-out';
171
172        switch (animation) {
173            case 'fade':
174            case 'slide':
175                element.style.opacity = '0';
176                element.style.maxHeight = '0';
177                void element.offsetHeight;
178                element.style.opacity = '1';
179                element.style.maxHeight = element.scrollHeight + 'px';
180                break;
181            case 'flip':
182                element.style.opacity = '0';
183                element.style.transform = 'rotateX(-90deg)';
184                void element.offsetHeight;
185                element.style.opacity = '1';
186                element.style.transform = 'rotateX(0deg)';
187                break;
188            case 'zoom':
189                element.style.opacity = '0';
190                element.style.transform = 'scale(0.8)';
191                void element.offsetHeight;
192                element.style.opacity = '1';
193                element.style.transform = 'scale(1)';
194                break;
195            case 'bounce':
196                element.style.opacity = '0';
197                element.style.transform = 'translateY(-20px)';
198                void element.offsetHeight;
199                element.style.transition = 'all ' + duration + 'ms cubic-bezier(0.68, -0.55, 0.265, 1.55)';
200                element.style.opacity = '1';
201                element.style.transform = 'translateY(0)';
202                break;
203            case 'rotate':
204                element.style.opacity = '0';
205                element.style.transform = 'rotate(-180deg) scale(0.8)';
206                void element.offsetHeight;
207                element.style.opacity = '1';
208                element.style.transform = 'rotate(0deg) scale(1)';
209                break;
210            case 'scale':
211                element.style.opacity = '0';
212                element.style.transform = 'scaleY(0)';
213                element.style.transformOrigin = 'top';
214                void element.offsetHeight;
215                element.style.opacity = '1';
216                element.style.transform = 'scaleY(1)';
217                break;
218            case 'elastic':
219                element.style.opacity = '0';
220                element.style.maxHeight = '0';
221                void element.offsetHeight;
222                element.style.transition = 'all ' + duration + 'ms cubic-bezier(0.68, -0.55, 0.265, 1.55)';
223                element.style.opacity = '1';
224                element.style.maxHeight = element.scrollHeight + 'px';
225                break;
226            default:
227                clearAnimStyles(element);
228                return;
229        }
230
231        window.setTimeout(function () { clearAnimStyles(element); }, duration);
232    }
233
234    function collapseEl(element, wrapper, instant) {
235        var animation = instant ? 'none' : ((wrapper && wrapper.dataset.animation) || 'none');
236        var duration = parseInt(wrapper ? wrapper.dataset.duration : 0, 10) || 300;
237
238        if (animation === 'none') {
239            element.style.display = 'none';
240            return;
241        }
242
243        element.style.transition = 'all ' + duration + 'ms ease-in-out';
244        element.style.overflow = 'hidden';
245
246        switch (animation) {
247            case 'fade':
248            case 'slide':
249            case 'elastic':
250                element.style.opacity = '1';
251                element.style.maxHeight = element.scrollHeight + 'px';
252                void element.offsetHeight;
253                element.style.opacity = '0';
254                element.style.maxHeight = '0';
255                break;
256            case 'flip':
257                void element.offsetHeight;
258                element.style.opacity = '0';
259                element.style.transform = 'rotateX(90deg)';
260                break;
261            case 'zoom':
262                void element.offsetHeight;
263                element.style.opacity = '0';
264                element.style.transform = 'scale(0.8)';
265                break;
266            case 'bounce':
267                void element.offsetHeight;
268                element.style.opacity = '0';
269                element.style.transform = 'translateY(-20px)';
270                break;
271            case 'rotate':
272                void element.offsetHeight;
273                element.style.opacity = '0';
274                element.style.transform = 'rotate(180deg) scale(0.8)';
275                break;
276            case 'scale':
277                element.style.transformOrigin = 'top';
278                void element.offsetHeight;
279                element.style.opacity = '0';
280                element.style.transform = 'scaleY(0)';
281                break;
282        }
283
284        window.setTimeout(function () {
285            element.style.display = 'none';
286            clearAnimStyles(element);
287        }, duration);
288    }
289
290    /* ---------------------------------------------------------------------
291     * Core toggle
292     * ------------------------------------------------------------------ */
293
294    function getParts(wrapper) {
295        var id = wrapper.dataset.id;
296        return {
297            id: id,
298            key: wrapper.dataset.key || id,
299            postId: wrapper.dataset.post || 0,
300            link: document.getElementById('readlink' + id),
301            content: document.getElementById('read' + id)
302        };
303    }
304
305    function isExpanded(content) {
306        return content.style.display !== 'none' && content.getAttribute('aria-hidden') !== 'true';
307    }
308
309    function setButtonText(link, text) {
310        var textEl = link.querySelector('.rmwr-text');
311        if (textEl) {
312            textEl.textContent = text;
313        } else {
314            link.textContent = text;
315        }
316    }
317
318    function materializeLazy(content) {
319        var tpl = content.querySelector('template.rmwr-tpl');
320        if (tpl) {
321            content.appendChild(tpl.content.cloneNode(true));
322            tpl.remove();
323        }
324    }
325
326    function rememberState(key, expanded) {
327        if (!settings.remember) {
328            return;
329        }
330        var map;
331        try { map = JSON.parse(storeGet('rmwr_state') || '{}'); } catch (e) { map = {}; }
332        if (expanded) {
333            map[key] = 1;
334        } else {
335            delete map[key];
336        }
337        storeSet('rmwr_state', JSON.stringify(map));
338    }
339
340    function toggle(wrapper, opts) {
341        opts = opts || {};
342        var parts = getParts(wrapper);
343        if (!parts.link || !parts.content) {
344            return;
345        }
346
347        var expanded = isExpanded(parts.content);
348        var openText = parts.link.dataset.openText || 'Read More';
349        var closeText = parts.link.dataset.closeText || 'Read Less';
350
351        if (!expanded) {
352            // Locker interception: show the unlock UI instead of the content.
353            if (wrapper.dataset.lock && !isUnlocked(parts.key)) {
354                showLocker(wrapper, parts);
355                return;
356            }
357
358            // Accordion: collapse the other items of the group first.
359            if (wrapper.dataset.mode === 'accordion' && wrapper.dataset.accordionId) {
360                document.querySelectorAll('[data-accordion-id="' + wrapper.dataset.accordionId + '"]').forEach(function (other) {
361                    if (other !== wrapper) {
362                        var op = getParts(other);
363                        if (op.link && op.content && isExpanded(op.content)) {
364                            collapseInstance(other, op);
365                        }
366                    }
367                });
368            }
369
370            materializeLazy(parts.content);
371            expandEl(parts.content, wrapper, opts.instant);
372            setButtonText(parts.link, closeText);
373            parts.link.setAttribute('aria-expanded', 'true');
374            parts.content.setAttribute('aria-hidden', 'false');
375
376            hideEllipsis(parts.id, true);
377
378            if (!opts.instant && wrapper.dataset.smoothScroll !== 'false') {
379                smoothScrollTo(parts.content, parseInt(wrapper.dataset.scrollOffset || '0', 10));
380            }
381
382            if (!opts.silent) {
383                var variant = parts.link.dataset.ab === '1' ? abVariant : '';
384                track(parts.key, parts.postId, 'expand', variant);
385            }
386
387            maybeShowCta(wrapper, parts);
388            rememberState(parts.key, true);
389        } else {
390            collapseInstance(wrapper, parts, opts);
391            rememberState(parts.key, false);
392        }
393    }
394
395    function collapseInstance(wrapper, parts, opts) {
396        opts = opts || {};
397        var openText = parts.link.dataset.openText || 'Read More';
398        collapseEl(parts.content, wrapper, opts.instant);
399        setButtonText(parts.link, openText);
400        parts.link.setAttribute('aria-expanded', 'false');
401        parts.content.setAttribute('aria-hidden', 'true');
402        hideEllipsis(parts.id, false);
403    }
404
405    function hideEllipsis(id, hidden) {
406        var el = document.querySelector('.rmwr-ellipsis[data-for="' + id + '"]');
407        if (el) {
408            el.style.display = hidden ? 'none' : '';
409        }
410    }
411
412    function smoothScrollTo(element, offset) {
413        if (!element) { return; }
414        var top = element.getBoundingClientRect().top + window.pageYOffset - (offset || 0);
415        window.scrollTo({ top: top, behavior: 'smooth' });
416    }
417
418    /* ---------------------------------------------------------------------
419     * Content Locker
420     * ------------------------------------------------------------------ */
421
422    function isUnlocked(key) {
423        return storeGet('rmwr_unlock_' + key) === '1';
424    }
425
426    function markUnlocked(key) {
427        storeSet('rmwr_unlock_' + key, '1');
428    }
429
430    function showLocker(wrapper, parts) {
431        var existing = wrapper.querySelector('.rmwr-lock-box');
432        if (existing) {
433            existing.style.display = '';
434            return;
435        }
436
437        var tpl = wrapper.querySelector('template.rmwr-lock-tpl');
438        if (!tpl) {
439            return;
440        }
441
442        wrapper.appendChild(tpl.content.cloneNode(true));
443        var box = wrapper.querySelector('.rmwr-lock-box');
444        if (!box) {
445            return;
446        }
447
448        var form = box.querySelector('.rmwr-lock-form');
449        if (form) {
450            form.addEventListener('submit', function (e) {
451                e.preventDefault();
452                submitLockerForm(form, wrapper, parts, box);
453            });
454        }
455
456        box.querySelectorAll('.rmwr-share-btn').forEach(function (btn) {
457            btn.addEventListener('click', function () {
458                openShareWindow(btn.dataset.network);
459                markUnlocked(parts.key);
460                track(parts.key, parts.postId, 'share');
461                box.remove();
462                toggle(wrapper, { silent: true });
463            });
464        });
465    }
466
467    function submitLockerForm(form, wrapper, parts, box) {
468        var locker = settings.locker || {};
469        var emailInput = form.querySelector('.rmwr-lock-email');
470        var errorEl = form.querySelector('.rmwr-lock-error');
471        var submitBtn = form.querySelector('.rmwr-lock-submit');
472        var consentInput = form.querySelector('input[name="rmwr_consent"]');
473        var email = emailInput ? emailInput.value.trim() : '';
474
475        function fail(message) {
476            if (errorEl) {
477                errorEl.textContent = message;
478                errorEl.hidden = false;
479            }
480            if (submitBtn) { submitBtn.disabled = false; }
481        }
482
483        if (!email || email.indexOf('@') < 1) {
484            fail(locker.invalidEmail || 'Please enter a valid email address.');
485            return;
486        }
487
488        if (submitBtn) { submitBtn.disabled = true; }
489        if (errorEl) { errorEl.hidden = true; }
490
491        window.fetch(settings.restUrl.replace(/\/$/, '') + '/unlock', {
492            method: 'POST',
493            headers: { 'Content-Type': 'application/json' },
494            body: JSON.stringify({
495                email: email,
496                k: parts.key,
497                p: parseInt(parts.postId, 10) || 0,
498                consent: !!(consentInput && consentInput.checked)
499            })
500        }).then(function (response) {
501            return response.json().then(function (data) { return { ok: response.ok, data: data }; });
502        }).then(function (result) {
503            if (result.ok && result.data && result.data.ok) {
504                markUnlocked(parts.key);
505                track(parts.key, parts.postId, 'unlock');
506                box.remove();
507                toggle(wrapper, { silent: true });
508            } else {
509                fail((settings.locker || {}).genericError || 'Something went wrong. Please try again.');
510            }
511        }).catch(function () {
512            fail((settings.locker || {}).genericError || 'Something went wrong. Please try again.');
513        });
514    }
515
516    function openShareWindow(network) {
517        var url = encodeURIComponent(window.location.href);
518        var title = encodeURIComponent(document.title);
519        var shareUrl = '';
520
521        if (network === 'facebook') {
522            shareUrl = 'https://www.facebook.com/sharer/sharer.php?u=' + url;
523        } else if (network === 'x') {
524            shareUrl = 'https://twitter.com/intent/tweet?url=' + url + '&text=' + title;
525        } else if (network === 'linkedin') {
526            shareUrl = 'https://www.linkedin.com/sharing/share-offsite/?url=' + url;
527        }
528
529        if (shareUrl) {
530            window.open(shareUrl, 'rmwr-share', 'width=600,height=500,noopener');
531        }
532    }
533
534    /* ---------------------------------------------------------------------
535     * Engaged-user CTA
536     * ------------------------------------------------------------------ */
537
538    function maybeShowCta(wrapper, parts) {
539        var cta = settings.cta || {};
540        if (wrapper.dataset.cta !== '1' || !cta.html || wrapper.dataset.ctaShown === '1') {
541            return;
542        }
543        wrapper.dataset.ctaShown = '1';
544
545        window.setTimeout(function () {
546            var box = document.createElement('div');
547            box.className = 'rmwr-cta-box';
548            box.innerHTML = cta.html;
549
550            var close = document.createElement('button');
551            close.type = 'button';
552            close.className = 'rmwr-cta-close';
553            close.setAttribute('aria-label', 'Close');
554            close.innerHTML = '&times;';
555            close.addEventListener('click', function () { box.remove(); });
556            box.appendChild(close);
557
558            box.addEventListener('click', function (e) {
559                if (e.target.closest('a, button:not(.rmwr-cta-close)')) {
560                    track(parts.key, parts.postId, 'cta');
561                }
562            });
563
564            parts.content.insertAdjacentElement('afterend', box);
565        }, (parseInt(cta.delay, 10) || 0) * 1000);
566    }
567
568    /* ---------------------------------------------------------------------
569     * Sections (Wikipedia mode)
570     * ------------------------------------------------------------------ */
571
572    function toggleSection(btn, forceOpen) {
573        var panel = document.getElementById(btn.getAttribute('aria-controls'));
574        if (!panel) {
575            return;
576        }
577        var open = btn.getAttribute('aria-expanded') === 'true';
578        if (forceOpen && open) {
579            return;
580        }
581        var next = forceOpen ? true : !open;
582        btn.setAttribute('aria-expanded', next ? 'true' : 'false');
583        panel.hidden = !next;
584    }
585
586    /* ---------------------------------------------------------------------
587     * Expand/Collapse all
588     * ------------------------------------------------------------------ */
589
590    function toggleAll(button) {
591        var group = button.dataset.group || '';
592        var wrappers = group
593            ? document.querySelectorAll('.rmwr-wrapper[data-accordion-id="' + group + '"]')
594            : document.querySelectorAll('.rmwr-wrapper');
595
596        var anyCollapsed = false;
597        wrappers.forEach(function (wrapper) {
598            var parts = getParts(wrapper);
599            if (parts.content && !isExpanded(parts.content)) {
600                anyCollapsed = true;
601            }
602        });
603
604        wrappers.forEach(function (wrapper) {
605            var parts = getParts(wrapper);
606            if (!parts.content || !parts.link) {
607                return;
608            }
609            if (anyCollapsed && !isExpanded(parts.content)) {
610                toggle(wrapper, { silent: true, instant: true });
611            } else if (!anyCollapsed && isExpanded(parts.content)) {
612                toggle(wrapper, { silent: true, instant: true });
613            }
614        });
615
616        // Sections too, when no specific group is set.
617        if (!group) {
618            document.querySelectorAll('.rmwr-sec-btn').forEach(function (btn) {
619                toggleSection(btn, anyCollapsed);
620            });
621        }
622
623        button.textContent = anyCollapsed
624            ? (button.dataset.closeText || 'Collapse all')
625            : (button.dataset.openText || 'Expand all');
626    }
627
628    /* ---------------------------------------------------------------------
629     * Conditional display
630     * ------------------------------------------------------------------ */
631
632    function applyConditionalDisplay(wrapper) {
633        var device = wrapper.dataset.device || 'all';
634        if (device !== 'all') {
635            var isMobile = window.innerWidth <= 768;
636            if ((device === 'mobile' && !isMobile) || (device === 'desktop' && isMobile)) {
637                wrapper.style.display = 'none';
638                return;
639            }
640        }
641
642        var showAfter = wrapper.dataset.showAfter;
643        if (showAfter) {
644            var seconds = parseInt(String(showAfter).replace('seconds', ''), 10) || 0;
645            if (seconds > 0) {
646                wrapper.style.visibility = 'hidden';
647                wrapper.style.opacity = '0';
648                window.setTimeout(function () {
649                    wrapper.style.transition = 'opacity 0.5s ease';
650                    wrapper.style.visibility = '';
651                    wrapper.style.opacity = '1';
652                }, seconds * 1000);
653            }
654        }
655
656        if (wrapper.dataset.scroll === 'true') {
657            wrapper.style.opacity = '0';
658            wrapper.style.transition = 'opacity 0.5s ease';
659            var reveal = function () {
660                var rect = wrapper.getBoundingClientRect();
661                var viewport = window.innerHeight || document.documentElement.clientHeight;
662                if (rect.top < viewport - 100) {
663                    wrapper.style.opacity = '1';
664                    window.removeEventListener('scroll', reveal);
665                }
666            };
667            window.addEventListener('scroll', reveal, { passive: true });
668            reveal();
669        }
670    }
671
672    /* ---------------------------------------------------------------------
673     * Deep linking
674     * ------------------------------------------------------------------ */
675
676    function handleHash() {
677        if (!settings.deepLink || !window.location.hash) {
678            return;
679        }
680
681        var hash = window.location.hash.slice(1);
682        if (!hash) {
683            return;
684        }
685
686        var target = null;
687        try { target = document.getElementById(hash); } catch (e) { return; }
688        if (!target) {
689            return;
690        }
691
692        // Section heading anchor (#rmwr-sec-slug).
693        if (target.classList.contains('rmwr-sec-title')) {
694            var btn = target.querySelector('.rmwr-sec-btn');
695            if (btn) {
696                toggleSection(btn, true);
697                window.setTimeout(function () { smoothScrollTo(target, 60); }, 50);
698            }
699            return;
700        }
701
702        // Wrapper / content id (#rmwr-p12-1 or #readrmwr-p12-1).
703        var wrapper = target.closest ? target.closest('.rmwr-wrapper') : null;
704        if (!wrapper) {
705            wrapper = document.querySelector('.rmwr-wrapper[data-id="' + hash + '"]');
706        }
707        if (wrapper) {
708            var parts = getParts(wrapper);
709            if (parts.content && !isExpanded(parts.content)) {
710                toggle(wrapper, { silent: true });
711            }
712            window.setTimeout(function () { smoothScrollTo(wrapper, 60); }, 50);
713            return;
714        }
715
716        // Element hidden inside a collapsed area or section panel.
717        var container = target.closest ? (target.closest('.read_div') || target.closest('.rmwr-sec-panel')) : null;
718        if (container) {
719            if (container.classList.contains('read_div')) {
720                var w = container.closest('.rmwr-wrapper');
721                if (w) { toggle(w, { silent: true }); }
722            } else {
723                var sbtn = document.querySelector('.rmwr-sec-btn[aria-controls="' + container.id + '"]');
724                if (sbtn) { toggleSection(sbtn, true); }
725            }
726            window.setTimeout(function () { smoothScrollTo(target, 60); }, 50);
727        }
728    }
729
730    /* ---------------------------------------------------------------------
731     * Init
732     * ------------------------------------------------------------------ */
733
734    function restoreRememberedState() {
735        if (!settings.remember) {
736            return;
737        }
738        var map;
739        try { map = JSON.parse(storeGet('rmwr_state') || '{}'); } catch (e) { map = {}; }
740
741        document.querySelectorAll('.rmwr-wrapper').forEach(function (wrapper) {
742            var parts = getParts(wrapper);
743            if (parts.key && map[parts.key] && parts.content && !isExpanded(parts.content) && !wrapper.dataset.lock) {
744                toggle(wrapper, { silent: true, instant: true });
745            }
746        });
747    }
748
749    function init() {
750        document.querySelectorAll('.rmwr-wrapper').forEach(applyConditionalDisplay);
751        initAB();
752        restoreRememberedState();
753        handleHash();
754
755        debug('initialized', document.querySelectorAll('.rmwr-wrapper').length, 'instances');
756
757        document.addEventListener('click', function (e) {
758            var secBtn = e.target.closest('.rmwr-sec-btn');
759            if (secBtn) {
760                e.preventDefault();
761                toggleSection(secBtn);
762                return;
763            }
764
765            var allBtn = e.target.closest('.rmwr-toggle-all');
766            if (allBtn) {
767                e.preventDefault();
768                toggleAll(allBtn);
769                return;
770            }
771
772            var tocLink = e.target.closest('.rmwr-toc a');
773            if (tocLink) {
774                // Let the browser set the hash; expansion follows on hashchange.
775                window.setTimeout(handleHash, 30);
776                return;
777            }
778
779            var link = e.target.closest('.read-link');
780            if (link && link.id && link.id.indexOf('readlink') === 0) {
781                e.preventDefault();
782                var wrapper = link.closest('.rmwr-wrapper');
783                if (wrapper) {
784                    toggle(wrapper);
785                }
786            }
787        });
788
789        window.addEventListener('hashchange', handleHash);
790
791        if (settings.printExpand !== false) {
792            window.addEventListener('beforeprint', function () {
793                document.querySelectorAll('.read_div').forEach(function (content) {
794                    materializeLazy(content);
795                    content.style.display = 'block';
796                    content.setAttribute('aria-hidden', 'false');
797                });
798                document.querySelectorAll('.rmwr-sec-panel').forEach(function (panel) {
799                    panel.hidden = false;
800                });
801            });
802        }
803
804        // Support content injected after load (AJAX pagination, quick view...).
805        if (typeof MutationObserver !== 'undefined' && document.body) {
806            var observer = new MutationObserver(function (mutations) {
807                mutations.forEach(function (mutation) {
808                    Array.prototype.forEach.call(mutation.addedNodes, function (node) {
809                        if (node.nodeType !== 1) {
810                            return;
811                        }
812                        var wrappers = node.classList && node.classList.contains('rmwr-wrapper')
813                            ? [node]
814                            : (node.querySelectorAll ? node.querySelectorAll('.rmwr-wrapper') : []);
815                        Array.prototype.forEach.call(wrappers, applyConditionalDisplay);
816                    });
817                });
818            });
819            observer.observe(document.body, { childList: true, subtree: true });
820        }
821
822        initReadingProgress();
823    }
824
825    /* ---------------------------------------------------------------------
826     * Reading progress bar + "continue reading" resume (free)
827     * ------------------------------------------------------------------ */
828
829    var progressStylesInjected = false;
830
831    function injectProgressStyles() {
832        if (progressStylesInjected) {
833            return;
834        }
835        progressStylesInjected = true;
836        var css = '.rmwr-progress{position:fixed;top:0;left:0;right:0;height:3px;z-index:99999;pointer-events:none;background:transparent}'
837            + '.rmwr-progress-fill{display:block;height:100%;width:0;background:var(--rmwr-progress-color,#7c3aed);transition:width .1s linear}'
838            + '.rmwr-resume{position:fixed;right:16px;bottom:16px;z-index:99999;background:var(--rmwr-progress-color,#7c3aed);color:#fff;border:0;border-radius:999px;padding:10px 16px;font:600 13px/1 system-ui,sans-serif;cursor:pointer;box-shadow:0 4px 14px rgba(0,0,0,.2)}'
839            + '.rmwr-resume:hover{filter:brightness(.92)}';
840        var style = document.createElement('style');
841        style.textContent = css;
842        document.head.appendChild(style);
843    }
844
845    function initReadingProgress() {
846        var cfg = settings.readingProgress;
847        if (!cfg || (!cfg.bar && !cfg.resume)) {
848            return;
849        }
850
851        injectProgressStyles();
852
853        if (cfg.bar) {
854            var bar = document.createElement('div');
855            bar.className = 'rmwr-progress';
856            bar.setAttribute('role', 'progressbar');
857            bar.setAttribute('aria-hidden', 'true');
858            var fill = document.createElement('span');
859            fill.className = 'rmwr-progress-fill';
860            bar.appendChild(fill);
861            document.body.appendChild(bar);
862
863            var barTicking = false;
864            var updateBar = function () {
865                var h = document.documentElement;
866                var max = (h.scrollHeight - h.clientHeight) || 1;
867                var top = h.scrollTop || window.pageYOffset || 0;
868                fill.style.width = Math.min(100, Math.max(0, (top / max) * 100)) + '%';
869                barTicking = false;
870            };
871            window.addEventListener('scroll', function () {
872                if (!barTicking) { barTicking = true; window.requestAnimationFrame(updateBar); }
873            }, { passive: true });
874            updateBar();
875        }
876
877        if (cfg.resume) {
878            initResume();
879        }
880    }
881
882    function initResume() {
883        var storeKey = 'rmwr_pos_' + location.pathname;
884
885        var resumeTicking = false;
886        window.addEventListener('scroll', function () {
887            if (resumeTicking) { return; }
888            resumeTicking = true;
889            window.requestAnimationFrame(function () {
890                try { window.localStorage.setItem(storeKey, String(window.pageYOffset || 0)); } catch (e) { /* noop */ }
891                resumeTicking = false;
892            });
893        }, { passive: true });
894
895        var saved = 0;
896        try { saved = parseInt(window.localStorage.getItem(storeKey), 10) || 0; } catch (e) { /* noop */ }
897        if (saved < 400) {
898            return; // Not worth offering for a tiny scroll.
899        }
900
901        var pill = document.createElement('button');
902        pill.type = 'button';
903        pill.className = 'rmwr-resume';
904        pill.textContent = settings.resumeText || 'Continue reading';
905        document.body.appendChild(pill);
906
907        var dismiss = function () {
908            if (pill.parentNode) { pill.parentNode.removeChild(pill); }
909        };
910        pill.addEventListener('click', function () {
911            try {
912                window.scrollTo({ top: saved, behavior: 'smooth' });
913            } catch (e) {
914                window.scrollTo(0, saved);
915            }
916            dismiss();
917        });
918        window.setTimeout(dismiss, 8000); // Auto-hide if ignored.
919    }
920
921    // Public API for theme developers.
922    window.RMWR = {
923        toggle: function (idOrKey) {
924            var wrapper = document.querySelector('.rmwr-wrapper[data-id="' + idOrKey + '"]')
925                || document.querySelector('.rmwr-wrapper[data-key="' + idOrKey + '"]');
926            if (wrapper) {
927                toggle(wrapper);
928            }
929        },
930        expandAll: function () {
931            document.querySelectorAll('.rmwr-wrapper').forEach(function (wrapper) {
932                var parts = getParts(wrapper);
933                if (parts.content && !isExpanded(parts.content)) {
934                    toggle(wrapper, { silent: true, instant: true });
935                }
936            });
937        },
938        track: track
939    };
940
941    if (document.readyState === 'loading') {
942        document.addEventListener('DOMContentLoaded', init);
943    } else {
944        init();
945    }
946})();

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.