PageSourceSearch

https://populartvnavarra.net/wp-content/themes/extendable/assets/js/animations-interactivity.js?ver=2.1.9

js populartvnavarra.net collected 2026-09-25 02:18:14 UTC 8,970 bytes, 261 lines download raw bytes

1(function() {
2    'use strict';
3
4    // Prevent execution if config is missing
5    if (!window.ExtendableAnimations) {
6        return;
7    }
8
9    const config = window.ExtendableAnimations || { map: {}, defaults: {} };
10    const staggerDelay = parseFloat(config.defaults && config.defaults.stagger) || 0.08;
11    const maxStagger = 0.6;
12    let observer = null;
13    let trackedElements = [];
14
15    const speeds = Object.freeze({
16        slow: 1,
17        medium: 0.6,
18        fast: 0.45
19    });
20    const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
21    let currentSpeed = config.speed || 'medium';
22    let currentType = config.type || 'none';
23    let currentMap = config.map || {};
24    const initialType = config.type || 'none'; // Track if PHP added FOUC CSS
25
26    function initAnimations() {
27        if (currentType === 'none' || prefersReducedMotion.matches) {
28            return;
29        }
30    
31        if (!('IntersectionObserver' in window)) {
32            Object.keys(currentMap).forEach(selector => {
33                try {
34                    document.querySelectorAll(selector).forEach(el => el.style.opacity = '1');
35                } catch (e) {}
36            });
37            return;
38        }
39
40        if (observer) {
41            observer.disconnect();
42        }
43
44        observer = new IntersectionObserver((entries) => {
45            entries.forEach(entry => {
46                if (entry.isIntersecting) {
47                    const element = entry.target;
48                    const animationType = element.dataset.extAnimate;
49                    
50                    if (animationType) {
51                        element.classList.add(`ext-animated-${animationType}`);
52                    }
53                }
54            });
55        }, {
56            threshold: 0.01,
57            rootMargin: '0px'
58        });
59
60        trackedElements = [];
61
62        const pending = [];
63        Object.keys(currentMap).forEach(selector => {
64            try {
65                const elements = document.querySelectorAll(selector);
66                const animationType = currentMap[selector];
67                const elementsByParent = new Map();
68                elements.forEach(element => {
69                    if (element.classList.contains('ext-animate--off')) {
70                        return;
71                    }
72                    const parent = element.parentElement;
73                    if (!elementsByParent.has(parent)) {
74                        elementsByParent.set(parent, []);
75                    }
76                    elementsByParent.get(parent).push(element);
77                });
78
79                elementsByParent.forEach(siblingElements => {
80                    siblingElements.forEach((element, index) => {
81                        const rect = element.getBoundingClientRect();
82                        pending.push({
83                            element,
84                            animationType,
85                            delay: Math.min(index * staggerDelay, maxStagger),
86                            // Elements already in viewport (like headers) - reveal immediately
87                            inViewport: rect.top < window.innerHeight && rect.bottom > 0,
88                        });
89                    });
90                });
91            } catch (e) {}
92        });
93
94        // Batch all writes into one frame so layout happens once, not once per element.
95        const duration = speeds[currentSpeed];
96        requestAnimationFrame(() => {
97            pending.forEach(({ element, animationType, delay, inViewport }) => {
98                element.classList.add('ext-animate');
99                element.dataset.extAnimate = animationType;
100                element.style.animationDuration = `${duration}s`;
101                element.style.animationDelay = `${delay}s`;
102
103                // Clear stacking context after animation completes
104                element.addEventListener('animationend', () => {
105                    element.dataset.extAnimated = 'true';
106                }, { once: true });
107
108                if (inViewport) {
109                    element.classList.add(`ext-animated-${animationType}`);
110                }
111
112                observer.observe(element);
113                trackedElements.push(element);
114            });
115        });
116    }
117
118    function resetAnimations() {
119        // Early return for reduced motion - nothing to reset
120        if (prefersReducedMotion.matches) {
121            return true;
122        }
123        
124        trackedElements.forEach(element => {
125            element.classList.remove(
126                'ext-animated-fade',
127                'ext-animated-fade-up',
128                'ext-animated-fade-down',
129                'ext-animated-fade-left',
130                'ext-animated-fade-right',
131                'ext-animated-zoom-in'
132            );
133            delete element.dataset.extAnimated;
134        });
135        
136        requestAnimationFrame(() => {
137            const visible = trackedElements.map(element => {
138                const rect = element.getBoundingClientRect();
139                return rect.top < window.innerHeight && rect.bottom > 0;
140            });
141
142            trackedElements.forEach((element, index) => {
143                if (!visible[index]) {
144                    return;
145                }
146                const animationType = element.dataset.extAnimate;
147                if (animationType) {
148                    element.classList.add(`ext-animated-${animationType}`);
149                }
150            });
151        });
152
153        return true;
154    }
155
156    function setSpeed(speed) {
157        // Early return for reduced motion - speed changes are irrelevant
158        if (prefersReducedMotion.matches) {
159            return true;
160        }
161        
162        if (!speeds[speed]) {
163            speed = 'medium';
164        }
165        currentSpeed = speed;
166        
167        trackedElements.forEach(element => {
168            element.style.animationDuration = `${speeds[currentSpeed]}s`;
169        });
170        
171        return true;
172    }
173
174    function setType(type) {
175        currentType = type || 'none';
176        
177        if (initialType === 'none') {
178            const existingStyle = document.getElementById('ext-fouc-styles');
179            if (existingStyle) {
180                existingStyle.remove();
181            }
182        }
183        
184        trackedElements.forEach(element => {
185            element.classList.remove(
186                'ext-animate',
187                'ext-animated-fade',
188                'ext-animated-fade-up',
189                'ext-animated-fade-down',
190                'ext-animated-fade-left',
191                'ext-animated-fade-right',
192                'ext-animated-zoom-in'
193            );
194            delete element.dataset.extAnimated;
195            element.style.opacity = '';
196            element.style.transform = '';
197            element.style.animationDuration = '';
198            element.style.animationDelay = '';
199        });
200        
201        trackedElements = [];
202        
203        if (observer) {
204            observer.disconnect();
205        }
206        
207        if (currentType === 'none') {
208            // Force elements visible (override PHP FOUC CSS)
209            Object.keys(config.allTypes?.['fade'] || currentMap || {}).forEach(selector => {
210                try {
211                    document.querySelectorAll(selector).forEach(el => {
212                        el.style.opacity = '1';
213                        el.style.transform = 'none';
214                    });
215                } catch (e) {}
216            });
217            return true;
218        }
219        
220        if (config.allTypes && config.allTypes[currentType]) {
221            currentMap = config.allTypes[currentType];
222        }
223        
224        Object.keys(currentMap).forEach(selector => {
225            try {
226                document.querySelectorAll(selector).forEach(el => {
227                    el.style.opacity = '';
228                });
229            } catch (e) {}
230        });
231        
232        // Only inject FOUC CSS if PHP didn't add it
233        if (initialType === 'none') {
234            const styleEl = document.createElement('style');
235            styleEl.id = 'ext-fouc-styles';
236            let css = '';
237            Object.keys(currentMap).forEach(selector => {
238                css += `@media (prefers-reduced-motion: no-preference) { ${selector}:not(.ext-animate--off) { opacity: 0; } } `;
239            });
240            styleEl.textContent = css;
241            document.head.appendChild(styleEl);
242        }
243        
244        requestAnimationFrame(() => {
245            initAnimations();
246        });
247        
248        return true;
249    }
250
251    window.ExtendableAnimations = window.ExtendableAnimations || {};
252    window.ExtendableAnimations.reset = resetAnimations;
253    window.ExtendableAnimations.setSpeed = setSpeed;
254    window.ExtendableAnimations.setType = setType;
255
256    if (document.readyState === 'loading') {
257        document.addEventListener('DOMContentLoaded', initAnimations);
258    } else {
259        initAnimations();
260    }
261})();

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.